564 lines
21 KiB
Java
564 lines
21 KiB
Java
/*
|
|
* Copyright (c) 2010, 2016, 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. Oracle designates this
|
|
* particular file as subject to the "Classpath" exception as provided
|
|
* by Oracle in the LICENSE file that accompanied this code.
|
|
*
|
|
* 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.
|
|
*/
|
|
|
|
package sun.security.util;
|
|
|
|
import java.security.CryptoPrimitive;
|
|
import java.security.AlgorithmParameters;
|
|
import java.security.Key;
|
|
import java.security.cert.CertPathValidatorException;
|
|
import java.security.cert.CertPathValidatorException.BasicReason;
|
|
import java.security.cert.X509Certificate;
|
|
import java.util.HashMap;
|
|
import java.util.HashSet;
|
|
import java.util.Locale;
|
|
import java.util.Map;
|
|
import java.util.Set;
|
|
import java.util.regex.Pattern;
|
|
import java.util.regex.Matcher;
|
|
|
|
/**
|
|
* Algorithm constraints for disabled algorithms property
|
|
*
|
|
* See the "jdk.certpath.disabledAlgorithms" specification in java.security
|
|
* for the syntax of the disabled algorithm string.
|
|
*/
|
|
public class DisabledAlgorithmConstraints extends AbstractAlgorithmConstraints {
|
|
private static final Debug debug = Debug.getInstance("certpath");
|
|
|
|
// the known security property, jdk.certpath.disabledAlgorithms
|
|
public final static String PROPERTY_CERTPATH_DISABLED_ALGS =
|
|
"jdk.certpath.disabledAlgorithms";
|
|
|
|
// the known security property, jdk.tls.disabledAlgorithms
|
|
public final static String PROPERTY_TLS_DISABLED_ALGS =
|
|
"jdk.tls.disabledAlgorithms";
|
|
|
|
// the known security property, jdk.jar.disabledAlgorithms
|
|
public static final String PROPERTY_JAR_DISABLED_ALGS =
|
|
"jdk.jar.disabledAlgorithms";
|
|
|
|
private final String[] disabledAlgorithms;
|
|
private final Constraints algorithmConstraints;
|
|
|
|
/**
|
|
* Initialize algorithm constraints with the specified security property.
|
|
*
|
|
* @param propertyName the security property name that define the disabled
|
|
* algorithm constraints
|
|
*/
|
|
public DisabledAlgorithmConstraints(String propertyName) {
|
|
this(propertyName, new AlgorithmDecomposer());
|
|
}
|
|
|
|
/**
|
|
* Initialize algorithm constraints with the specified security property
|
|
* for a specific usage type.
|
|
*
|
|
* @param propertyName the security property name that define the disabled
|
|
* algorithm constraints
|
|
* @param decomposer an alternate AlgorithmDecomposer.
|
|
*/
|
|
public DisabledAlgorithmConstraints(String propertyName,
|
|
AlgorithmDecomposer decomposer) {
|
|
super(decomposer);
|
|
disabledAlgorithms = getAlgorithms(propertyName);
|
|
algorithmConstraints = new Constraints(disabledAlgorithms);
|
|
}
|
|
|
|
/*
|
|
* This only checks if the algorithm has been completely disabled. If
|
|
* there are keysize or other limit, this method allow the algorithm.
|
|
*/
|
|
@Override
|
|
final public boolean permits(Set<CryptoPrimitive> primitives,
|
|
String algorithm, AlgorithmParameters parameters) {
|
|
|
|
if (primitives == null || primitives.isEmpty()) {
|
|
throw new IllegalArgumentException(
|
|
"No cryptographic primitive specified");
|
|
}
|
|
|
|
return checkAlgorithm(disabledAlgorithms, algorithm, decomposer);
|
|
}
|
|
|
|
/*
|
|
* Checks if the key algorithm has been disabled or constraints have been
|
|
* placed on the key.
|
|
*/
|
|
@Override
|
|
final public boolean permits(Set<CryptoPrimitive> primitives, Key key) {
|
|
return checkConstraints(primitives, "", key, null);
|
|
}
|
|
|
|
/*
|
|
* Checks if the key algorithm has been disabled or if constraints have
|
|
* been placed on the key.
|
|
*/
|
|
@Override
|
|
final public boolean permits(Set<CryptoPrimitive> primitives,
|
|
String algorithm, Key key, AlgorithmParameters parameters) {
|
|
|
|
if (algorithm == null || algorithm.length() == 0) {
|
|
throw new IllegalArgumentException("No algorithm name specified");
|
|
}
|
|
|
|
return checkConstraints(primitives, algorithm, key, parameters);
|
|
}
|
|
|
|
/*
|
|
* Check if a x509Certificate object is permitted. Check if all
|
|
* algorithms are allowed, certificate constraints, and the
|
|
* public key against key constraints.
|
|
*
|
|
* Uses new style permit() which throws exceptions.
|
|
*/
|
|
public final void permits(Set<CryptoPrimitive> primitives,
|
|
CertConstraintParameters cp) throws CertPathValidatorException {
|
|
checkConstraints(primitives, cp);
|
|
}
|
|
|
|
/*
|
|
* Check if Certificate object is within the constraints.
|
|
* Uses new style permit() which throws exceptions.
|
|
*/
|
|
public final void permits(Set<CryptoPrimitive> primitives,
|
|
X509Certificate cert) throws CertPathValidatorException {
|
|
checkConstraints(primitives, new CertConstraintParameters(cert));
|
|
}
|
|
|
|
// Check if a string is contained inside the property
|
|
public boolean checkProperty(String param) {
|
|
param = param.toLowerCase(Locale.ENGLISH);
|
|
for (String block : disabledAlgorithms) {
|
|
if (block.toLowerCase(Locale.ENGLISH).indexOf(param) >= 0) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// Check algorithm constraints with key and algorithm
|
|
private boolean checkConstraints(Set<CryptoPrimitive> primitives,
|
|
String algorithm, Key key, AlgorithmParameters parameters) {
|
|
|
|
// check the key parameter, it cannot be null.
|
|
if (key == null) {
|
|
throw new IllegalArgumentException("The key cannot be null");
|
|
}
|
|
|
|
// check the signature algorithm
|
|
if (algorithm != null && algorithm.length() != 0) {
|
|
if (!permits(primitives, algorithm, parameters)) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// check the key algorithm
|
|
if (!permits(primitives, key.getAlgorithm(), null)) {
|
|
return false;
|
|
}
|
|
|
|
// check the key constraints
|
|
return algorithmConstraints.permits(key);
|
|
}
|
|
|
|
/*
|
|
* Check algorithm constraints with Certificate
|
|
* Uses new style permit() which throws exceptions.
|
|
*/
|
|
private void checkConstraints(Set<CryptoPrimitive> primitives,
|
|
CertConstraintParameters cp) throws CertPathValidatorException {
|
|
|
|
X509Certificate cert = cp.getCertificate();
|
|
String algorithm = cert.getSigAlgName();
|
|
|
|
// Check signature algorithm is not disabled
|
|
if (!permits(primitives, algorithm, null)) {
|
|
throw new CertPathValidatorException(
|
|
"Algorithm constraints check failed on disabled "+
|
|
"signature algorithm: " + algorithm,
|
|
null, null, -1, BasicReason.ALGORITHM_CONSTRAINED);
|
|
}
|
|
|
|
// Check key algorithm is not disabled
|
|
if (!permits(primitives, cert.getPublicKey().getAlgorithm(), null)) {
|
|
throw new CertPathValidatorException(
|
|
"Algorithm constraints check failed on disabled "+
|
|
"public key algorithm: " + algorithm,
|
|
null, null, -1, BasicReason.ALGORITHM_CONSTRAINED);
|
|
}
|
|
|
|
// Check the certificate and key constraints
|
|
algorithmConstraints.permits(cp);
|
|
|
|
}
|
|
|
|
/**
|
|
* Key and Certificate Constraints
|
|
*
|
|
* The complete disabling of an algorithm is not handled by Constraints or
|
|
* Constraint classes. That is addressed with
|
|
* permit(Set<CryptoPrimitive>, String, AlgorithmParameters)
|
|
*
|
|
* When passing a Key to permit(), the boolean return values follow the
|
|
* same as the interface class AlgorithmConstraints.permit(). This is to
|
|
* maintain compatibility:
|
|
* 'true' means the operation is allowed.
|
|
* 'false' means it failed the constraints and is disallowed.
|
|
*
|
|
* When passing CertConstraintParameters through permit(), an exception
|
|
* will be thrown on a failure to better identify why the operation was
|
|
* disallowed.
|
|
*/
|
|
|
|
private static class Constraints {
|
|
private Map<String, Set<Constraint>> constraintsMap = new HashMap<>();
|
|
private static final Pattern keySizePattern = Pattern.compile(
|
|
"keySize\\s*(<=|<|==|!=|>|>=)\\s*(\\d+)");
|
|
|
|
public Constraints(String[] constraintArray) {
|
|
for (String constraintEntry : constraintArray) {
|
|
if (constraintEntry == null || constraintEntry.isEmpty()) {
|
|
continue;
|
|
}
|
|
|
|
constraintEntry = constraintEntry.trim();
|
|
if (debug != null) {
|
|
debug.println("Constraints: " + constraintEntry);
|
|
}
|
|
|
|
// Check if constraint is a complete disabling of an
|
|
// algorithm or has conditions.
|
|
String algorithm;
|
|
String policy;
|
|
int space = constraintEntry.indexOf(' ');
|
|
if (space > 0) {
|
|
algorithm = AlgorithmDecomposer.hashName(
|
|
constraintEntry.substring(0, space).
|
|
toUpperCase(Locale.ENGLISH));
|
|
policy = constraintEntry.substring(space + 1);
|
|
} else {
|
|
constraintsMap.putIfAbsent(
|
|
constraintEntry.toUpperCase(Locale.ENGLISH),
|
|
new HashSet<>());
|
|
continue;
|
|
}
|
|
|
|
// Convert constraint conditions into Constraint classes
|
|
Constraint c = null;
|
|
Constraint lastConstraint = null;
|
|
// Allow only one jdkCA entry per constraint entry
|
|
boolean jdkCALimit = false;
|
|
|
|
for (String entry : policy.split("&")) {
|
|
entry = entry.trim();
|
|
|
|
Matcher matcher = keySizePattern.matcher(entry);
|
|
if (matcher.matches()) {
|
|
if (debug != null) {
|
|
debug.println("Constraints set to keySize: " +
|
|
entry);
|
|
}
|
|
c = new KeySizeConstraint(algorithm,
|
|
KeySizeConstraint.Operator.of(matcher.group(1)),
|
|
Integer.parseInt(matcher.group(2)));
|
|
|
|
} else if (entry.equalsIgnoreCase("jdkCA")) {
|
|
if (debug != null) {
|
|
debug.println("Constraints set to jdkCA.");
|
|
}
|
|
if (jdkCALimit) {
|
|
throw new IllegalArgumentException("Only one " +
|
|
"jdkCA entry allowed in property. " +
|
|
"Constraint: " + constraintEntry);
|
|
}
|
|
c = new jdkCAConstraint(algorithm);
|
|
jdkCALimit = true;
|
|
}
|
|
|
|
// Link multiple conditions for a single constraint
|
|
// into a linked list.
|
|
if (lastConstraint == null) {
|
|
if (!constraintsMap.containsKey(algorithm)) {
|
|
constraintsMap.putIfAbsent(algorithm,
|
|
new HashSet<>());
|
|
}
|
|
if (c != null) {
|
|
constraintsMap.get(algorithm).add(c);
|
|
}
|
|
} else {
|
|
lastConstraint.nextConstraint = c;
|
|
}
|
|
lastConstraint = c;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Get applicable constraints based off the signature algorithm
|
|
private Set<Constraint> getConstraints(String algorithm) {
|
|
return constraintsMap.get(algorithm);
|
|
}
|
|
|
|
// Check if KeySizeConstraints permit the specified key
|
|
public boolean permits(Key key) {
|
|
Set<Constraint> set = getConstraints(key.getAlgorithm());
|
|
if (set == null) {
|
|
return true;
|
|
}
|
|
for (Constraint constraint : set) {
|
|
if (!constraint.permits(key)) {
|
|
if (debug != null) {
|
|
debug.println("keySizeConstraint: failed key " +
|
|
"constraint check " + KeyUtil.getKeySize(key));
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// Check if constraints permit this cert.
|
|
public void permits(CertConstraintParameters cp)
|
|
throws CertPathValidatorException {
|
|
X509Certificate cert = cp.getCertificate();
|
|
|
|
if (debug != null) {
|
|
debug.println("Constraints.permits(): " + cert.getSigAlgName());
|
|
}
|
|
|
|
// Get all signature algorithms to check for constraints
|
|
Set<String> algorithms =
|
|
AlgorithmDecomposer.decomposeOneHash(cert.getSigAlgName());
|
|
if (algorithms == null || algorithms.isEmpty()) {
|
|
return;
|
|
}
|
|
|
|
// Attempt to add the public key algorithm to the set
|
|
algorithms.add(cert.getPublicKey().getAlgorithm());
|
|
|
|
// Check all applicable constraints
|
|
for (String algorithm : algorithms) {
|
|
Set<Constraint> set = getConstraints(algorithm);
|
|
if (set == null) {
|
|
continue;
|
|
}
|
|
for (Constraint constraint : set) {
|
|
constraint.permits(cp);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Abstract class for algorithm constraint checking
|
|
private abstract static class Constraint {
|
|
String algorithm;
|
|
Constraint nextConstraint = null;
|
|
|
|
// operator
|
|
enum Operator {
|
|
EQ, // "=="
|
|
NE, // "!="
|
|
LT, // "<"
|
|
LE, // "<="
|
|
GT, // ">"
|
|
GE; // ">="
|
|
|
|
static Operator of(String s) {
|
|
switch (s) {
|
|
case "==":
|
|
return EQ;
|
|
case "!=":
|
|
return NE;
|
|
case "<":
|
|
return LT;
|
|
case "<=":
|
|
return LE;
|
|
case ">":
|
|
return GT;
|
|
case ">=":
|
|
return GE;
|
|
}
|
|
|
|
throw new IllegalArgumentException("Error in security " +
|
|
"property. " + s + " is not a legal Operator");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Check if an algorithm constraint permit this key to be used.
|
|
* @param key Public key
|
|
* @return true if constraints do not match
|
|
*/
|
|
public boolean permits(Key key) {
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Check if an algorithm constraint is permit this certificate to
|
|
* be used.
|
|
* @param cp CertificateParameter containing certificate and state info
|
|
* @return true if constraints do not match
|
|
*/
|
|
public abstract void permits(CertConstraintParameters cp)
|
|
throws CertPathValidatorException;
|
|
}
|
|
|
|
/*
|
|
* This class contains constraints dealing with the certificate chain
|
|
* of the certificate.
|
|
*/
|
|
private static class jdkCAConstraint extends Constraint {
|
|
jdkCAConstraint(String algo) {
|
|
algorithm = algo;
|
|
}
|
|
|
|
/*
|
|
* Check if each constraint fails and check if there is a linked
|
|
* constraint Any permitted constraint will exit the linked list
|
|
* to allow the operation.
|
|
*/
|
|
public void permits(CertConstraintParameters cp)
|
|
throws CertPathValidatorException {
|
|
if (debug != null) {
|
|
debug.println("jdkCAConstraints.permits(): " + algorithm);
|
|
}
|
|
|
|
// Return false if the chain has a trust anchor in cacerts
|
|
if (cp.isTrustedMatch()) {
|
|
if (nextConstraint != null) {
|
|
nextConstraint.permits(cp);
|
|
return;
|
|
}
|
|
throw new CertPathValidatorException(
|
|
"Algorithm constraints check failed on certificate " +
|
|
"anchor limits",
|
|
null, null, -1, BasicReason.ALGORITHM_CONSTRAINED);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* This class contains constraints dealing with the key size
|
|
* support limits per algorithm. e.g. "keySize <= 1024"
|
|
*/
|
|
private static class KeySizeConstraint extends Constraint {
|
|
|
|
private int minSize; // the minimal available key size
|
|
private int maxSize; // the maximal available key size
|
|
private int prohibitedSize = -1; // unavailable key sizes
|
|
|
|
public KeySizeConstraint(String algo, Operator operator, int length) {
|
|
algorithm = algo;
|
|
switch (operator) {
|
|
case EQ: // an unavailable key size
|
|
this.minSize = 0;
|
|
this.maxSize = Integer.MAX_VALUE;
|
|
prohibitedSize = length;
|
|
break;
|
|
case NE:
|
|
this.minSize = length;
|
|
this.maxSize = length;
|
|
break;
|
|
case LT:
|
|
this.minSize = length;
|
|
this.maxSize = Integer.MAX_VALUE;
|
|
break;
|
|
case LE:
|
|
this.minSize = length + 1;
|
|
this.maxSize = Integer.MAX_VALUE;
|
|
break;
|
|
case GT:
|
|
this.minSize = 0;
|
|
this.maxSize = length;
|
|
break;
|
|
case GE:
|
|
this.minSize = 0;
|
|
this.maxSize = length > 1 ? (length - 1) : 0;
|
|
break;
|
|
default:
|
|
// unlikely to happen
|
|
this.minSize = Integer.MAX_VALUE;
|
|
this.maxSize = -1;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* If we are passed a certificate, extract the public key and use it.
|
|
*
|
|
* Check if each constraint fails and check if there is a linked
|
|
* constraint Any permitted constraint will exit the linked list
|
|
* to allow the operation.
|
|
*/
|
|
public void permits(CertConstraintParameters cp)
|
|
throws CertPathValidatorException {
|
|
if (!permitsImpl(cp.getCertificate().getPublicKey())) {
|
|
if (nextConstraint != null) {
|
|
nextConstraint.permits(cp);
|
|
return;
|
|
}
|
|
throw new CertPathValidatorException(
|
|
"Algorithm constraints check failed on keysize limits",
|
|
null, null, -1, BasicReason.ALGORITHM_CONSTRAINED);
|
|
}
|
|
}
|
|
|
|
|
|
// Check if key constraint disable the specified key
|
|
// Uses old style permit()
|
|
public boolean permits(Key key) {
|
|
// If we recursively find a constraint that permits us to use
|
|
// this key, return true and skip any other constraint checks.
|
|
if (nextConstraint != null && nextConstraint.permits(key)) {
|
|
return true;
|
|
}
|
|
if (debug != null) {
|
|
debug.println("KeySizeConstraints.permits(): " + algorithm);
|
|
}
|
|
|
|
return permitsImpl(key);
|
|
}
|
|
|
|
private boolean permitsImpl(Key key) {
|
|
// Verify this constraint is for this public key algorithm
|
|
if (algorithm.compareToIgnoreCase(key.getAlgorithm()) != 0) {
|
|
return true;
|
|
}
|
|
|
|
int size = KeyUtil.getKeySize(key);
|
|
if (size == 0) {
|
|
return false; // we don't allow any key of size 0.
|
|
} else if (size > 0) {
|
|
return !((size < minSize) || (size > maxSize) ||
|
|
(prohibitedSize == size));
|
|
} // Otherwise, the key size is not accessible. Conservatively,
|
|
// please don't disable such keys.
|
|
|
|
return true;
|
|
}
|
|
}
|
|
}
|