blob: 17ba912fb4397ab81923ff6162ee160ade8ccaca [file] [log] [blame]
/*
* Copyright (c) 2015, 2018, 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.ssl;
import java.security.*;
import java.security.interfaces.ECPrivateKey;
import java.security.spec.AlgorithmParameterSpec;
import java.security.spec.ECParameterSpec;
import java.security.spec.MGF1ParameterSpec;
import java.security.spec.PSSParameterSpec;
import java.util.AbstractMap.SimpleImmutableEntry;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import sun.security.ssl.SupportedGroupsExtension.NamedGroup;
import sun.security.ssl.SupportedGroupsExtension.NamedGroupType;
import sun.security.ssl.SupportedGroupsExtension.SupportedGroups;
import sun.security.ssl.X509Authentication.X509Possession;
import sun.security.util.KeyUtil;
import sun.security.util.SignatureUtil;
enum SignatureScheme {
// EdDSA algorithms
ED25519 (0x0807, "ed25519", "ed25519",
"ed25519",
ProtocolVersion.PROTOCOLS_OF_13),
ED448 (0x0808, "ed448", "ed448",
"ed448",
ProtocolVersion.PROTOCOLS_OF_13),
// ECDSA algorithms
ECDSA_SECP256R1_SHA256 (0x0403, "ecdsa_secp256r1_sha256",
"SHA256withECDSA",
"EC",
NamedGroup.SECP256_R1,
ProtocolVersion.PROTOCOLS_TO_13),
ECDSA_SECP384R1_SHA384 (0x0503, "ecdsa_secp384r1_sha384",
"SHA384withECDSA",
"EC",
NamedGroup.SECP384_R1,
ProtocolVersion.PROTOCOLS_TO_13),
ECDSA_SECP521R1_SHA512 (0x0603, "ecdsa_secp521r1_sha512",
"SHA512withECDSA",
"EC",
NamedGroup.SECP521_R1,
ProtocolVersion.PROTOCOLS_TO_13),
// RSASSA-PSS algorithms with public key OID rsaEncryption
//
// The minimalKeySize is calculated as (See RFC 8017 for details):
// hash length + salt length + 16
RSA_PSS_RSAE_SHA256 (0x0804, "rsa_pss_rsae_sha256",
"RSASSA-PSS", "RSA",
SigAlgParamSpec.RSA_PSS_SHA256, 528,
ProtocolVersion.PROTOCOLS_12_13),
RSA_PSS_RSAE_SHA384 (0x0805, "rsa_pss_rsae_sha384",
"RSASSA-PSS", "RSA",
SigAlgParamSpec.RSA_PSS_SHA384, 784,
ProtocolVersion.PROTOCOLS_12_13),
RSA_PSS_RSAE_SHA512 (0x0806, "rsa_pss_rsae_sha512",
"RSASSA-PSS", "RSA",
SigAlgParamSpec.RSA_PSS_SHA512, 1040,
ProtocolVersion.PROTOCOLS_12_13),
// RSASSA-PSS algorithms with public key OID RSASSA-PSS
//
// The minimalKeySize is calculated as (See RFC 8017 for details):
// hash length + salt length + 16
RSA_PSS_PSS_SHA256 (0x0809, "rsa_pss_pss_sha256",
"RSASSA-PSS", "RSASSA-PSS",
SigAlgParamSpec.RSA_PSS_SHA256, 528,
ProtocolVersion.PROTOCOLS_12_13),
RSA_PSS_PSS_SHA384 (0x080A, "rsa_pss_pss_sha384",
"RSASSA-PSS", "RSASSA-PSS",
SigAlgParamSpec.RSA_PSS_SHA384, 784,
ProtocolVersion.PROTOCOLS_12_13),
RSA_PSS_PSS_SHA512 (0x080B, "rsa_pss_pss_sha512",
"RSASSA-PSS", "RSASSA-PSS",
SigAlgParamSpec.RSA_PSS_SHA512, 1040,
ProtocolVersion.PROTOCOLS_12_13),
// RSASSA-PKCS1-v1_5 algorithms
RSA_PKCS1_SHA256 (0x0401, "rsa_pkcs1_sha256", "SHA256withRSA",
"RSA", null, null, 511,
ProtocolVersion.PROTOCOLS_TO_13,
ProtocolVersion.PROTOCOLS_TO_12),
RSA_PKCS1_SHA384 (0x0501, "rsa_pkcs1_sha384", "SHA384withRSA",
"RSA", null, null, 768,
ProtocolVersion.PROTOCOLS_TO_13,
ProtocolVersion.PROTOCOLS_TO_12),
RSA_PKCS1_SHA512 (0x0601, "rsa_pkcs1_sha512", "SHA512withRSA",
"RSA", null, null, 768,
ProtocolVersion.PROTOCOLS_TO_13,
ProtocolVersion.PROTOCOLS_TO_12),
// Legacy algorithms
DSA_SHA256 (0x0402, "dsa_sha256", "SHA256withDSA",
"DSA",
ProtocolVersion.PROTOCOLS_TO_12),
ECDSA_SHA224 (0x0303, "ecdsa_sha224", "SHA224withECDSA",
"EC",
ProtocolVersion.PROTOCOLS_TO_12),
RSA_SHA224 (0x0301, "rsa_sha224", "SHA224withRSA",
"RSA", 511,
ProtocolVersion.PROTOCOLS_TO_12),
DSA_SHA224 (0x0302, "dsa_sha224", "SHA224withDSA",
"DSA",
ProtocolVersion.PROTOCOLS_TO_12),
ECDSA_SHA1 (0x0203, "ecdsa_sha1", "SHA1withECDSA",
"EC",
ProtocolVersion.PROTOCOLS_TO_13),
RSA_PKCS1_SHA1 (0x0201, "rsa_pkcs1_sha1", "SHA1withRSA",
"RSA", null, null, 511,
ProtocolVersion.PROTOCOLS_TO_13,
ProtocolVersion.PROTOCOLS_TO_12),
DSA_SHA1 (0x0202, "dsa_sha1", "SHA1withDSA",
"DSA",
ProtocolVersion.PROTOCOLS_TO_12),
RSA_MD5 (0x0101, "rsa_md5", "MD5withRSA",
"RSA", 511,
ProtocolVersion.PROTOCOLS_TO_12);
final int id; // hash + signature
final String name; // literal name
private final String algorithm; // signature algorithm
final String keyAlgorithm; // signature key algorithm
private final AlgorithmParameterSpec signAlgParameter;
private final NamedGroup namedGroup; // associated named group
// The minimal required key size in bits.
//
// Only need to check RSA algorithm at present. RSA keys of 512 bits
// have been shown to be practically breakable, it does not make much
// sense to use the strong hash algorithm for keys whose key size less
// than 512 bits. So it is not necessary to calculate the minimal
// required key size exactly for a hash algorithm.
//
// Note that some provider may use 511 bits for 512-bit strength RSA keys.
final int minimalKeySize;
final List<ProtocolVersion> supportedProtocols;
// Some signature schemes are supported in different versions for handshake
// messages and certificates. This field holds the supported protocols
// for handshake messages.
final List<ProtocolVersion> handshakeSupportedProtocols;
final boolean isAvailable;
private static final String[] hashAlgorithms = new String[] {
"none", "md5", "sha1", "sha224",
"sha256", "sha384", "sha512"
};
private static final String[] signatureAlgorithms = new String[] {
"anonymous", "rsa", "dsa", "ecdsa",
};
static enum SigAlgParamSpec { // support RSASSA-PSS only now
RSA_PSS_SHA256 ("SHA-256", 32),
RSA_PSS_SHA384 ("SHA-384", 48),
RSA_PSS_SHA512 ("SHA-512", 64);
final private AlgorithmParameterSpec parameterSpec;
final boolean isAvailable;
SigAlgParamSpec(String hash, int saltLength) {
// See RFC 8017
PSSParameterSpec pssParamSpec =
new PSSParameterSpec(hash, "MGF1",
new MGF1ParameterSpec(hash), saltLength, 1);
boolean mediator = true;
try {
Signature signer = JsseJce.getSignature("RSASSA-PSS");
signer.setParameter(pssParamSpec);
} catch (InvalidAlgorithmParameterException |
NoSuchAlgorithmException exp) {
mediator = false;
if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
SSLLogger.warning(
"RSASSA-PSS signature with " + hash +
" is not supported by the underlying providers", exp);
}
}
this.isAvailable = mediator;
this.parameterSpec = mediator ? pssParamSpec : null;
}
AlgorithmParameterSpec getParameterSpec() {
return parameterSpec;
}
}
// performance optimization
private static final Set<CryptoPrimitive> SIGNATURE_PRIMITIVE_SET =
Collections.unmodifiableSet(EnumSet.of(CryptoPrimitive.SIGNATURE));
private SignatureScheme(int id, String name,
String algorithm, String keyAlgorithm,
ProtocolVersion[] supportedProtocols) {
this(id, name, algorithm, keyAlgorithm, -1, supportedProtocols);
}
private SignatureScheme(int id, String name,
String algorithm, String keyAlgorithm,
int minimalKeySize,
ProtocolVersion[] supportedProtocols) {
this(id, name, algorithm, keyAlgorithm,
null, minimalKeySize, supportedProtocols);
}
private SignatureScheme(int id, String name,
String algorithm, String keyAlgorithm,
SigAlgParamSpec signAlgParamSpec, int minimalKeySize,
ProtocolVersion[] supportedProtocols) {
this(id, name, algorithm, keyAlgorithm,
signAlgParamSpec, null, minimalKeySize,
supportedProtocols, supportedProtocols);
}
private SignatureScheme(int id, String name,
String algorithm, String keyAlgorithm,
NamedGroup namedGroup,
ProtocolVersion[] supportedProtocols) {
this(id, name, algorithm, keyAlgorithm,
null, namedGroup, -1,
supportedProtocols, supportedProtocols);
}
private SignatureScheme(int id, String name,
String algorithm, String keyAlgorithm,
SigAlgParamSpec signAlgParamSpec,
NamedGroup namedGroup, int minimalKeySize,
ProtocolVersion[] supportedProtocols,
ProtocolVersion[] handshakeSupportedProtocols) {
this.id = id;
this.name = name;
this.algorithm = algorithm;
this.keyAlgorithm = keyAlgorithm;
this.signAlgParameter =
signAlgParamSpec != null ? signAlgParamSpec.parameterSpec : null;
this.namedGroup = namedGroup;
this.minimalKeySize = minimalKeySize;
this.supportedProtocols = Arrays.asList(supportedProtocols);
this.handshakeSupportedProtocols =
Arrays.asList(handshakeSupportedProtocols);
boolean mediator = true;
// An EC provider, for example the SunEC provider, may support
// AlgorithmParameters but not KeyPairGenerator or Signature.
//
// Note: Please be careful if removing this block!
if ("EC".equals(keyAlgorithm)) {
mediator = JsseJce.isEcAvailable();
}
// Check the specific algorithm and parameters.
if (mediator) {
if (signAlgParamSpec != null) {
mediator = signAlgParamSpec.isAvailable;
} else {
try {
JsseJce.getSignature(algorithm);
} catch (Exception e) {
mediator = false;
if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
SSLLogger.warning(
"Signature algorithm, " + algorithm +
", is not supported by the underlying providers");
}
}
}
}
if (mediator && ((id >> 8) & 0xFF) == 0x03) { // SHA224
// There are some problems to use SHA224 on Windows.
if (Security.getProvider("SunMSCAPI") != null) {
mediator = false;
}
}
this.isAvailable = mediator;
}
static SignatureScheme valueOf(int id) {
for (SignatureScheme ss: SignatureScheme.values()) {
if (ss.id == id) {
return ss;
}
}
return null;
}
static String nameOf(int id) {
for (SignatureScheme ss: SignatureScheme.values()) {
if (ss.id == id) {
return ss.name;
}
}
// Use TLS 1.2 style name for unknown signature scheme.
int hashId = ((id >> 8) & 0xFF);
int signId = (id & 0xFF);
String hashName = (hashId >= hashAlgorithms.length) ?
"UNDEFINED-HASH(" + hashId + ")" : hashAlgorithms[hashId];
String signName = (signId >= signatureAlgorithms.length) ?
"UNDEFINED-SIGNATURE(" + signId + ")" :
signatureAlgorithms[signId];
return signName + "_" + hashName;
}
// Return the size of a SignatureScheme structure in TLS record
static int sizeInRecord() {
return 2;
}
// Get local supported algorithm collection complying to algorithm
// constraints.
static List<SignatureScheme> getSupportedAlgorithms(
AlgorithmConstraints constraints,
List<ProtocolVersion> activeProtocols) {
List<SignatureScheme> supported = new LinkedList<>();
for (SignatureScheme ss: SignatureScheme.values()) {
if (!ss.isAvailable) {
continue;
}
boolean isMatch = false;
for (ProtocolVersion pv : activeProtocols) {
if (ss.supportedProtocols.contains(pv)) {
isMatch = true;
break;
}
}
if (isMatch) {
if (constraints.permits(
SIGNATURE_PRIMITIVE_SET, ss.algorithm, null)) {
supported.add(ss);
} else if (SSLLogger.isOn &&
SSLLogger.isOn("ssl,handshake,verbose")) {
SSLLogger.finest(
"Ignore disabled signature scheme: " + ss.name);
}
} else if (SSLLogger.isOn &&
SSLLogger.isOn("ssl,handshake,verbose")) {
SSLLogger.finest(
"Ignore inactive signature scheme: " + ss.name);
}
}
return supported;
}
static List<SignatureScheme> getSupportedAlgorithms(
AlgorithmConstraints constraints,
ProtocolVersion protocolVersion, int[] algorithmIds) {
List<SignatureScheme> supported = new LinkedList<>();
for (int ssid : algorithmIds) {
SignatureScheme ss = SignatureScheme.valueOf(ssid);
if (ss == null) {
if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
SSLLogger.warning(
"Unsupported signature scheme: " +
SignatureScheme.nameOf(ssid));
}
} else if (ss.isAvailable &&
ss.supportedProtocols.contains(protocolVersion) &&
constraints.permits(SIGNATURE_PRIMITIVE_SET,
ss.algorithm, null)) {
supported.add(ss);
} else {
if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
SSLLogger.warning(
"Unsupported signature scheme: " + ss.name);
}
}
}
return supported;
}
static SignatureScheme getPreferableAlgorithm(
List<SignatureScheme> schemes,
SignatureScheme certScheme,
ProtocolVersion version) {
for (SignatureScheme ss : schemes) {
if (ss.isAvailable &&
ss.handshakeSupportedProtocols.contains(version) &&
certScheme.keyAlgorithm.equalsIgnoreCase(ss.keyAlgorithm)) {
return ss;
}
}
return null;
}
static Map.Entry<SignatureScheme, Signature> getSignerOfPreferableAlgorithm(
List<SignatureScheme> schemes,
X509Possession x509Possession,
ProtocolVersion version) {
PrivateKey signingKey = x509Possession.popPrivateKey;
String keyAlgorithm = signingKey.getAlgorithm();
int keySize;
// Only need to check RSA algorithm at present.
if (keyAlgorithm.equalsIgnoreCase("RSA") ||
keyAlgorithm.equalsIgnoreCase("RSASSA-PSS")) {
keySize = KeyUtil.getKeySize(signingKey);
} else {
keySize = Integer.MAX_VALUE;
}
for (SignatureScheme ss : schemes) {
if (ss.isAvailable && (keySize >= ss.minimalKeySize) &&
ss.handshakeSupportedProtocols.contains(version) &&
keyAlgorithm.equalsIgnoreCase(ss.keyAlgorithm)) {
if (ss.namedGroup != null &&
ss.namedGroup.type == NamedGroupType.NAMED_GROUP_ECDHE) {
ECParameterSpec params =
x509Possession.getECParameterSpec();
if (params != null &&
ss.namedGroup == NamedGroup.valueOf(params)) {
Signature signer = ss.getSigner(signingKey);
if (signer != null) {
return new SimpleImmutableEntry<>(ss, signer);
}
}
if (SSLLogger.isOn &&
SSLLogger.isOn("ssl,handshake,verbose")) {
SSLLogger.finest(
"Ignore the signature algorithm (" + ss +
"), unsupported EC parameter spec: " + params);
}
} else if ("EC".equals(ss.keyAlgorithm)) {
// Must be a legacy signature algorithm, which does not
// specify the associated named groups. The connection
// cannot be established if the peer cannot recognize
// the named group used for the signature. RFC 8446
// does not define countermeasures for the corner cases.
// In order to mitigate the impact, we choose to check
// against the local supported named groups. The risk
// should be minimal as applications should not use
// unsupported named groups for its certificates.
ECParameterSpec params =
x509Possession.getECParameterSpec();
if (params != null) {
NamedGroup keyGroup = NamedGroup.valueOf(params);
if (keyGroup != null &&
SupportedGroups.isSupported(keyGroup)) {
Signature signer = ss.getSigner(signingKey);
if (signer != null) {
return new SimpleImmutableEntry<>(ss, signer);
}
}
}
if (SSLLogger.isOn &&
SSLLogger.isOn("ssl,handshake,verbose")) {
SSLLogger.finest(
"Ignore the legacy signature algorithm (" + ss +
"), unsupported EC parameter spec: " + params);
}
} else {
Signature signer = ss.getSigner(signingKey);
if (signer != null) {
return new SimpleImmutableEntry<>(ss, signer);
}
}
}
}
return null;
}
static String[] getAlgorithmNames(Collection<SignatureScheme> schemes) {
if (schemes != null) {
ArrayList<String> names = new ArrayList<>(schemes.size());
for (SignatureScheme scheme : schemes) {
names.add(scheme.algorithm);
}
return names.toArray(new String[0]);
}
return new String[0];
}
// This method is used to get the signature instance of this signature
// scheme for the specific public key. Unlike getSigner(), the exception
// is bubbled up. If the public key does not support this signature
// scheme, it normally means the TLS handshaking cannot continue and
// the connection should be terminated.
Signature getVerifier(PublicKey publicKey) throws NoSuchAlgorithmException,
InvalidAlgorithmParameterException, InvalidKeyException {
if (!isAvailable) {
return null;
}
Signature verifier = Signature.getInstance(algorithm);
SignatureUtil.initVerifyWithParam(verifier, publicKey, signAlgParameter);
return verifier;
}
// This method is also used to choose preferable signature scheme for the
// specific private key. If the private key does not support the signature
// scheme, {@code null} is returned, and the caller may fail back to next
// available signature scheme.
private Signature getSigner(PrivateKey privateKey) {
if (!isAvailable) {
return null;
}
try {
Signature signer = Signature.getInstance(algorithm);
SignatureUtil.initSignWithParam(signer, privateKey,
signAlgParameter,
null);
return signer;
} catch (NoSuchAlgorithmException | InvalidKeyException |
InvalidAlgorithmParameterException nsae) {
if (SSLLogger.isOn &&
SSLLogger.isOn("ssl,handshake,verbose")) {
SSLLogger.finest(
"Ignore unsupported signature algorithm (" +
this.name + ")", nsae);
}
}
return null;
}
}