Skip to content
Closed
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2009, 2022, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2009, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
Expand Down Expand Up @@ -37,6 +37,7 @@

import sun.security.util.Debug;
import sun.security.util.DisabledAlgorithmConstraints;
import sun.security.util.KeyUtil;
import sun.security.validator.Validator;
import sun.security.x509.AlgorithmId;
import sun.security.x509.X509CertImpl;
Expand Down Expand Up @@ -206,7 +207,8 @@ public void check(Certificate cert,
CertPathConstraintsParameters cp =
new CertPathConstraintsParameters(trustedPubKey, variant,
anchor, date);
dac.permits(trustedPubKey.getAlgorithm(), cp, true);
dac.permits(KeyUtil.getAlgorithm(trustedPubKey),
Copy link
Member

@artur-oracle artur-oracle Mar 18, 2025

Choose a reason for hiding this comment

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

Do you plan to have a unit test for AlgorithmChecker changes? It looks like certificates using ED25519 algorithm didn't match that check before. It would be useful to have a test where we disable ED25519 in java.security and then try to use a certificate with ED25519 algorithm.

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 checked by an existing test

cp, true);
}
// Check the signature algorithm and parameters against constraints
CertPathConstraintsParameters cp =
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2010, 2024, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2010, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
Expand Down Expand Up @@ -34,18 +34,15 @@
import java.security.cert.CertPathValidatorException;
import java.security.cert.CertPathValidatorException.BasicReason;
import java.security.interfaces.ECKey;
import java.security.interfaces.XECKey;
import java.security.spec.AlgorithmParameterSpec;
import java.security.spec.InvalidParameterSpecException;
import java.security.spec.MGF1ParameterSpec;
import java.security.spec.NamedParameterSpec;
import java.security.spec.PSSParameterSpec;
import java.time.DateTimeException;
import java.time.Instant;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
Expand Down Expand Up @@ -254,7 +251,7 @@ public final void permits(String algorithm, ConstraintsParameters cp,
if (checkKey) {
// Check if named curves in the key are disabled.
for (Key key : cp.getKeys()) {
for (String curve : getNamedCurveFromKey(key)) {
for (String curve : getNamedParametersFromKey(key)) {
if (!cachedCheckAlgorithm(curve)) {
throw new CertPathValidatorException(
"Algorithm constraints check failed on disabled " +
Expand All @@ -267,17 +264,23 @@ public final void permits(String algorithm, ConstraintsParameters cp,
algorithmConstraints.permits(algorithm, cp, checkKey);
}

private static List<String> getNamedCurveFromKey(Key key) {
if (key instanceof ECKey) {
NamedCurve nc = CurveDB.lookup(((ECKey)key).getParams());
return (nc == null ? List.of()
: Arrays.asList(nc.getNameAndAliases()));
} else if (key instanceof XECKey) {
return List.of(
((NamedParameterSpec)((XECKey)key).getParams()).getName());
} else {
return List.of();
}
private static List<String> getNamedParametersFromKey(Key key) {
return switch (key) {
case ECKey ecKey -> {
NamedCurve nc = CurveDB.lookup(ecKey.getParams());
if (nc == null) {
yield List.of();
}
yield List.of(nc.getNameAndAliases());
Copy link
Contributor

Choose a reason for hiding this comment

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

Do you want to add EC itself to the list? I am asking because for EdDSA you added both the algorithm name and the parameter set name.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

No, "EC" isn't an alias for a particular curve.

}
default -> {
String n = KeyUtil.getAlgorithm(key);
if (n.equalsIgnoreCase(key.getAlgorithm())) {
yield List.of(n);
}
yield List.of(key.getAlgorithm(), n);
}
};
}

// Check algorithm constraints with key and algorithm
Expand All @@ -301,12 +304,12 @@ private boolean checkConstraints(Set<CryptoPrimitive> primitives,
}

// check the key algorithm
if (!permits(primitives, key.getAlgorithm(), null)) {
if (!permits(primitives, KeyUtil.getAlgorithm(key), null)) {
return false;
}

// If this is an elliptic curve, check if it is disabled
for (String curve : getNamedCurveFromKey(key)) {
for (String curve : getNamedParametersFromKey(key)) {
if (!permits(primitives, curve, null)) {
return false;
}
Expand Down Expand Up @@ -460,7 +463,7 @@ private List<Constraint> getConstraints(String algorithm) {

// Check if KeySizeConstraints permit the specified key
public boolean permits(Key key) {
List<Constraint> list = getConstraints(key.getAlgorithm());
List<Constraint> list = getConstraints(KeyUtil.getAlgorithm(key));
if (list == null) {
return true;
}
Expand Down Expand Up @@ -514,7 +517,7 @@ public void permits(String algorithm, ConstraintsParameters cp,

if (checkKey) {
for (Key key : cp.getKeys()) {
algorithms.add(key.getAlgorithm());
algorithms.add(KeyUtil.getAlgorithm(key));
}
}

Expand Down
16 changes: 16 additions & 0 deletions src/java.base/share/classes/sun/security/util/KeyUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,22 @@ public static final int getKeySize(AlgorithmParameters parameters) {
return -1;
}

/**
* 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 may be "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
Contributor

Choose a reason for hiding this comment

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

Maybe just say this allows more precise check for DAC. For KeyPairGenerator, "EdDSA" by default means "Ed25519", but you can always call init(NamedParameterSpec.ED448) to change it.

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 see what your saying, but I was only explaining when EdDSA & Ed25519 can mean the same with KPG. As this is an internal method, I wasn't trying to explaining how to generate an Ed488 key.

Copy link
Contributor

Choose a reason for hiding this comment

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

OK.

*/
public static String getAlgorithm(Key key) {
if (key instanceof AsymmetricKey ak &&
ak.getParams() instanceof NamedParameterSpec nps) {
return nps.getName();
}
return key.getAlgorithm();
}

/**
* Returns whether the key is valid or not.
* <P>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
/*
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/

import sun.security.util.DisabledAlgorithmConstraints;

import java.security.CryptoPrimitive;
import java.security.KeyPairGenerator;
import java.security.PrivateKey;
import java.security.Security;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.Set;

/*
* @test
* @bug 8346129
* @modules java.base/sun.security.util
* @summary Check DisabledAlgorithmConstraints using EdDSA & XDH against
* permit(Set<CryptoPrimitive>, String, AP) and
* permit(Set<CryptoPrimitive>, Key). Results will differ based
* on method used. The first method only can compare the String with the
* algorithms. The second can check the algorithm and NamedParameterSpec
* while results in more 'false' cases.
*
* @run main/othervm DisabledAlgorithmPermits Ed25519
* @run main/othervm DisabledAlgorithmPermits Ed448
* @run main/othervm DisabledAlgorithmPermits EdDSA
* @run main/othervm DisabledAlgorithmPermits X25519
* @run main/othervm DisabledAlgorithmPermits X448
* @run main/othervm DisabledAlgorithmPermits XDH
*/

public class DisabledAlgorithmPermits {
public static void main(String[] args) throws Exception {
String algorithm = args[0];
Security.setProperty("x", algorithm);

// Expected table are the expected results from the test
List<TestCase> expected = switch (algorithm) {
case "Ed25519" ->
Arrays.asList(
new TestCase("EdDSA", true),
new TestCase("Ed25519", false),
new TestCase("Ed448", true),
new TestCase("X448", true),
new TestCase(1,"EdDSA", false),
new TestCase(1,"Ed25519", false),
new TestCase(1,"Ed448", true),
new TestCase(1,"X448", true));
case "Ed448" ->
Arrays.asList(
new TestCase("EdDSA", true),
new TestCase("Ed25519", true),
new TestCase("Ed448", false),
new TestCase("X448", true),
new TestCase(1,"EdDSA", true),
new TestCase(1,"Ed25519", true),
new TestCase(1,"Ed448", false),
new TestCase(1,"X448", true));
case "EdDSA" ->
Arrays.asList(
new TestCase("EdDSA", false),
new TestCase("Ed25519", true),
Copy link
Contributor

@wangweij wangweij Mar 7, 2025

Choose a reason for hiding this comment

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

Why should the above pass? If you disable EdDSA and you are still allowed Signature.getInstance("Ed25519")? If this is because it will reject whatever EdDSA key later? Why both check CryptoPrimitive.SIGNATURE at all?

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'm confused by this comment. With removing the hardcoded aliases in AbstractAlgorithmConstraints, which is what I thought you had suggested, EdDSA and Ed25519 are now separate as the check is effectively a string compare check against the disabledAlgorithm list

The second half of that case statement has a key that can check against both EdDSA and the NPS.

With respect to CryptoPrimitive.SIGNATURE, it just a value used in the test, it can't be null.

Copy link
Contributor

Choose a reason for hiding this comment

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

Yes, I remember that.

I understand there will be multiple checks in TLS and CertPath. Do we have existing tests on that level?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We have other tests that check this.

new TestCase("Ed448", true),
new TestCase("X448", true),
new TestCase(1,"EdDSA", false),
new TestCase(1,"Ed25519", false),
new TestCase(1,"Ed448", false),
new TestCase(1,"X448", true));
case "X25519" ->
Arrays.asList(
new TestCase("XDH", true),
new TestCase("X25519", false),
new TestCase("X448", true),
new TestCase("Ed448", true),
new TestCase(1, "XDH", false),
new TestCase(1, "X25519", false),
new TestCase(1, "X448", true),
new TestCase(1, "Ed448", true));
case "X448" ->
Arrays.asList(
new TestCase("XDH", true),
new TestCase("X25519", true),
new TestCase("X448", false),
new TestCase("Ed448", true),
new TestCase(1, "XDH", true),
new TestCase(1, "X25519", true),
new TestCase(1, "X448", false),
new TestCase(1, "Ed448", true));
case "XDH" ->
Arrays.asList(
new TestCase("XDH", false),
new TestCase("X25519", true),
new TestCase("X448", true),
new TestCase("Ed448", true),
new TestCase(1, "XDH", false),
new TestCase(1, "X25519", false),
new TestCase(1, "X448", false),
new TestCase(1, "Ed448", true));
default -> null;
};

Objects.requireNonNull(expected, "algorithm being tested " +
algorithm + " not in expected table");
System.out.println("---");
var dac = new DisabledAlgorithmConstraints("x");
System.out.println("disabled algorithms = " + Security.getProperty("x"));

// Using only testType 0, this tests that permit(Set<>, String, null)
// will check only the algorithm against the disabled list
expected.stream().filter(n->n.testType == 0).forEach(tc -> {
boolean r = dac.permits(Set.of(CryptoPrimitive.SIGNATURE),
tc.testAlgo, null);
System.out.print("\tpermits(Set.of(CryptoPrimitive.SIGNATURE), \"" +
tc.testAlgo + "\", null): " + r + " : " );
if (r != tc.expected) {
System.out.println("failed.");
throw new AssertionError("failed. Expected " +
tc.expected);
}
Copy link
Member

Choose a reason for hiding this comment

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

I suggest replacing this check with assertEquals.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

If I did that, then I couldn't println to stdout. I'd rather keep it as is.

System.out.println("pass");
});

// Using only testType 1, this tests permit(Set<>, Key) that will look
// at both the key.getAlgorithm() and the key.getParams().getName()
// against the disabled list
expected.stream().filter(n->n.testType == 1).forEach(tc -> {
PrivateKey k;
try {
k = KeyPairGenerator.getInstance(tc.testAlgo).generateKeyPair().
getPrivate();
} catch (Exception e) {
throw new RuntimeException(e);
}
boolean r = dac.permits(Set.of(CryptoPrimitive.SIGNATURE), k);
System.out.print("\tpermits(Set.of(CryptoPrimitive.SIGNATURE), " +
tc.testAlgo + " privkey): " + r + " : " );
if (r != tc.expected) {
System.out.println("failed.");
throw new AssertionError("failed. Expected " +
tc.expected);
}
Copy link
Member

Choose a reason for hiding this comment

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

Same as above.

System.out.println("pass");
});
System.out.println("---");
}

record TestCase(int testType, String testAlgo, boolean expected) {
TestCase(String testAlgo, boolean expected) {
this(0, testAlgo, expected);
}
}
}