Authors: russell@unturf.com · brackishbert@gmail.com · foxhop.net · TimeHexOn.com Patches, unit tests, benchmarks, whitepaper, and outreach briefs. Public domain — no copyright claimed. Use freely.
246 lines
9.7 KiB
Java
246 lines
9.7 KiB
Java
/*
|
|
* Copyright (c) 2021, 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 java.nio.ByteBuffer;
|
|
import java.util.AbstractMap.SimpleImmutableEntry;
|
|
import java.util.ArrayList;
|
|
import java.util.List;
|
|
import java.util.Map;
|
|
import java.util.Objects;
|
|
import javax.net.ssl.SSLEngine;
|
|
import javax.net.ssl.SSLException;
|
|
|
|
/**
|
|
* This is not a test. Actual tests are implemented by concrete subclasses.
|
|
* The abstract class AbstractCheckSignatureSchemes provides a base framework
|
|
* for checking TLS signature schemes.
|
|
*/
|
|
|
|
public abstract class AbstractCheckSignatureSchemes extends SSLEngineTemplate {
|
|
|
|
// Helper map to correlate integral SignatureScheme identifiers to
|
|
// their IANA string name counterparts.
|
|
protected static final Map<Integer, String> sigSchemeMap = Map.ofEntries(
|
|
new SimpleImmutableEntry(0x0401, "rsa_pkcs1_sha256"),
|
|
new SimpleImmutableEntry(0x0501, "rsa_pkcs1_sha384"),
|
|
new SimpleImmutableEntry(0x0601, "rsa_pkcs1_sha512"),
|
|
new SimpleImmutableEntry(0x0403, "ecdsa_secp256r1_sha256"),
|
|
new SimpleImmutableEntry(0x0503, "ecdsa_secp384r1_sha384"),
|
|
new SimpleImmutableEntry(0x0603, "ecdsa_secp521r1_sha512"),
|
|
new SimpleImmutableEntry(0x0804, "rsa_pss_rsae_sha256"),
|
|
new SimpleImmutableEntry(0x0805, "rsa_pss_rsae_sha384"),
|
|
new SimpleImmutableEntry(0x0806, "rsa_pss_rsae_sha512"),
|
|
new SimpleImmutableEntry(0x0807, "ed25519"),
|
|
new SimpleImmutableEntry(0x0808, "ed448"),
|
|
new SimpleImmutableEntry(0x0809, "rsa_pss_pss_sha256"),
|
|
new SimpleImmutableEntry(0x080a, "rsa_pss_pss_sha384"),
|
|
new SimpleImmutableEntry(0x080b, "rsa_pss_pss_sha512"),
|
|
new SimpleImmutableEntry(0x0101, "rsa_md5"),
|
|
new SimpleImmutableEntry(0x0201, "rsa_pkcs1_sha1"),
|
|
new SimpleImmutableEntry(0x0202, "dsa_sha1"),
|
|
new SimpleImmutableEntry(0x0203, "ecdsa_sha1"),
|
|
new SimpleImmutableEntry(0x0301, "rsa_sha224"),
|
|
new SimpleImmutableEntry(0x0302, "dsa_sha224"),
|
|
new SimpleImmutableEntry(0x0303, "ecdsa_sha224"),
|
|
new SimpleImmutableEntry(0x0402, "rsa_pkcs1_sha256"));
|
|
|
|
// Other useful TLS definitions for these tests
|
|
protected static final int TLS_HS_CLI_HELLO = 1;
|
|
protected static final int TLS_HS_CERT_REQ = 13;
|
|
protected static final int SIG_ALGS_EXT = 13;
|
|
protected static final int SIG_ALGS_CERT_EXT = 50;
|
|
|
|
protected AbstractCheckSignatureSchemes() throws Exception {
|
|
super();
|
|
}
|
|
|
|
// Returns the protocol for test to use.
|
|
abstract String getProtocol();
|
|
|
|
protected boolean isDtls() {
|
|
return getProtocol().startsWith("DTLS");
|
|
}
|
|
|
|
@Override
|
|
protected SSLEngine configureClientEngine(SSLEngine clientEngine) {
|
|
clientEngine.setUseClientMode(true);
|
|
clientEngine.setEnabledProtocols(new String[]{getProtocol()});
|
|
return clientEngine;
|
|
}
|
|
|
|
@Override
|
|
protected SSLEngine configureServerEngine(SSLEngine serverEngine) {
|
|
serverEngine.setUseClientMode(false);
|
|
serverEngine.setWantClientAuth(true);
|
|
serverEngine.setEnabledProtocols(new String[]{getProtocol()});
|
|
return serverEngine;
|
|
}
|
|
|
|
@Override
|
|
protected ContextParameters getServerContextParameters() {
|
|
return new ContextParameters(getProtocol(), "PKIX", "NewSunX509");
|
|
}
|
|
|
|
@Override
|
|
protected ContextParameters getClientContextParameters() {
|
|
return new ContextParameters(getProtocol(), "PKIX", "NewSunX509");
|
|
}
|
|
|
|
protected ByteBuffer extractHandshakeMsg(ByteBuffer tlsRecord, int hsMsgId)
|
|
throws SSLException {
|
|
return extractHandshakeMsg(tlsRecord, hsMsgId, isDtls());
|
|
}
|
|
|
|
/**
|
|
* Parses the ClientHello message and extracts from it a list of
|
|
* SignatureScheme values in string form. It is assumed that the provided
|
|
* ByteBuffer has its position set at the first byte of the ClientHello
|
|
* message body (AFTER the handshake header) and contains the entire
|
|
* hello message. Upon successful completion of this method the ByteBuffer
|
|
* will have its position reset to the initial offset in the buffer.
|
|
* If an exception is thrown the position at the time of the exception
|
|
* will be preserved.
|
|
*
|
|
* @param data The ByteBuffer containing the ClientHello bytes.
|
|
* @param extCode Code of the TLS extension from which to extract
|
|
* signature schemes.
|
|
* @return A List of the signature schemes in string form.
|
|
*/
|
|
protected List<String> getSigSchemesCliHello(
|
|
ByteBuffer data, int extCode) {
|
|
Objects.requireNonNull(data);
|
|
data.mark();
|
|
|
|
// Skip over the protocol version and client random
|
|
data.position(data.position() + 34);
|
|
|
|
// Jump past the session ID (if there is one)
|
|
int sessLen = Byte.toUnsignedInt(data.get());
|
|
if (sessLen != 0) {
|
|
data.position(data.position() + sessLen);
|
|
}
|
|
|
|
// Skip DTLS-specific opaque cookie if any
|
|
if (isDtls()) {
|
|
int cookieLen = Byte.toUnsignedInt(data.get());
|
|
if (cookieLen != 0) {
|
|
data.position(data.position() + cookieLen);
|
|
}
|
|
}
|
|
|
|
// Jump past the cipher suites
|
|
int csLen = Short.toUnsignedInt(data.getShort());
|
|
if (csLen != 0) {
|
|
data.position(data.position() + csLen);
|
|
}
|
|
|
|
// ...and the compression
|
|
int compLen = Byte.toUnsignedInt(data.get());
|
|
if (compLen != 0) {
|
|
data.position(data.position() + compLen);
|
|
}
|
|
|
|
// Now for the fun part. Go through the extensions and look
|
|
// for the two status request exts.
|
|
List<String> extSigAlgs = getSigSchemesFromExt(data, extCode);
|
|
|
|
// We should be at the end of the ClientHello
|
|
data.reset();
|
|
return extSigAlgs;
|
|
}
|
|
|
|
/**
|
|
* Parses the CertificateRequest message and extracts from it a list of
|
|
* SignatureScheme values in string form. It is assumed that the provided
|
|
* ByteBuffer has its position set at the first byte of the
|
|
* CertificateRequest message body (AFTER the handshake header) and
|
|
* contains the entire CR message. Upon successful completion of this
|
|
* method the ByteBuffer will have its position reset to the initial
|
|
* offset in the buffer.
|
|
* If an exception is thrown the position at the time of the exception
|
|
* will be preserved.
|
|
*
|
|
* @param data The ByteBuffer containing the CertificateRequest bytes
|
|
*
|
|
* @return A List of the signature schemes in string form. If no
|
|
* signature_algorithms extension is present in the CertificateRequest
|
|
* then an empty list will be returned.
|
|
*/
|
|
protected List<String> getSigSchemesCertReq(ByteBuffer data) {
|
|
Objects.requireNonNull(data);
|
|
data.mark();
|
|
|
|
// Jump past the certificate types
|
|
int certTypeLen = Byte.toUnsignedInt(data.get());
|
|
if (certTypeLen != 0) {
|
|
data.position(data.position() + certTypeLen);
|
|
}
|
|
|
|
// Collect the SignatureAndHashAlgorithms
|
|
List<String> extSigAlgs = new ArrayList();
|
|
int sigSchemeLen = Short.toUnsignedInt(data.getShort());
|
|
for (int ssOff = 0; ssOff < sigSchemeLen; ssOff += 2) {
|
|
String schemeName = sigSchemeMap.get(
|
|
Short.toUnsignedInt(data.getShort()));
|
|
if (schemeName != null) {
|
|
extSigAlgs.add(schemeName);
|
|
}
|
|
}
|
|
|
|
data.reset();
|
|
return extSigAlgs;
|
|
}
|
|
|
|
/**
|
|
* Gets signatures schemes from the given TLS extension.
|
|
* The buffer should be positioned at the start of the extension.
|
|
*/
|
|
protected List<String> getSigSchemesFromExt(
|
|
ByteBuffer data, int extCode) {
|
|
|
|
List<String> extSigAlgs = new ArrayList<>();
|
|
data.getShort(); // read length
|
|
|
|
while (data.hasRemaining()) {
|
|
int extType = Short.toUnsignedInt(data.getShort());
|
|
int extLen = Short.toUnsignedInt(data.getShort());
|
|
if (extType == extCode) {
|
|
// Start processing signature algorithms
|
|
int sigSchemeLen = Short.toUnsignedInt(data.getShort());
|
|
for (int ssOff = 0; ssOff < sigSchemeLen; ssOff += 2) {
|
|
String schemeName = sigSchemeMap.get(
|
|
Short.toUnsignedInt(data.getShort()));
|
|
if (schemeName != null) {
|
|
extSigAlgs.add(schemeName);
|
|
}
|
|
}
|
|
} else {
|
|
// Not the extension we're looking for. Skip past the
|
|
// extension data
|
|
data.position(data.position() + extLen);
|
|
}
|
|
}
|
|
|
|
return extSigAlgs;
|
|
}
|
|
}
|