using System;
using System.Collections.Generic;
using System.IO;
using Org.BouncyCastle.Tls.Crypto;
using Org.BouncyCastle.Utilities;
namespace Org.BouncyCastle.Tls
{
/// Base class for a TLS server.
public abstract class AbstractTlsServer
: AbstractTlsPeer, TlsServer
{
protected TlsServerContext m_context;
protected ProtocolVersion[] m_protocolVersions;
protected int[] m_cipherSuites;
protected int[] m_offeredCipherSuites;
protected IDictionary m_clientExtensions;
protected bool m_encryptThenMACOffered;
protected short m_maxFragmentLengthOffered;
protected bool m_truncatedHMacOffered;
protected bool m_clientSentECPointFormats;
protected CertificateStatusRequest m_certificateStatusRequest;
protected IList m_statusRequestV2;
protected IList m_trustedCAKeys;
protected int m_selectedCipherSuite;
protected IList m_clientProtocolNames;
protected ProtocolName m_selectedProtocolName;
protected readonly IDictionary m_serverExtensions = new Dictionary();
public AbstractTlsServer(TlsCrypto crypto)
: base(crypto)
{
}
protected virtual bool AllowCertificateStatus()
{
return true;
}
protected virtual bool AllowEncryptThenMac()
{
return true;
}
protected virtual bool AllowMultiCertStatus()
{
return false;
}
protected virtual bool AllowTruncatedHmac()
{
return false;
}
protected virtual bool AllowTrustedCAIndication()
{
return false;
}
protected virtual string GetDetailMessageNoCipherSuite()
{
return "No selectable cipher suite";
}
protected virtual int GetMaximumNegotiableCurveBits()
{
int maxBits = 0;
int[] clientSupportedGroups = m_context.SecurityParameters.ClientSupportedGroups;
if (clientSupportedGroups != null)
{
for (int i = 0; i < clientSupportedGroups.Length; ++i)
{
maxBits = System.Math.Max(maxBits, NamedGroup.GetCurveBits(clientSupportedGroups[i]));
}
}
else
{
/*
* RFC 4492 4. A client that proposes ECC cipher suites may choose not to include these
* extensions. In this case, the server is free to choose any one of the elliptic curves or point
* formats [...].
*/
maxBits = NamedGroup.GetMaximumCurveBits();
}
return maxBits;
}
protected virtual int GetMaximumNegotiableFiniteFieldBits()
{
int maxBits = 0;
bool anyPeerFF = false;
int[] clientSupportedGroups = m_context.SecurityParameters.ClientSupportedGroups;
if (clientSupportedGroups != null)
{
for (int i = 0; i < clientSupportedGroups.Length; ++i)
{
anyPeerFF |= NamedGroup.IsFiniteField(clientSupportedGroups[i]);
maxBits = System.Math.Max(maxBits, NamedGroup.GetFiniteFieldBits(clientSupportedGroups[i]));
}
}
if (!anyPeerFF)
{
/*
* RFC 7919 4. If [...] the Supported Groups extension is either absent from the ClientHello
* entirely or contains no FFDHE groups (i.e., no codepoints between 256 and 511, inclusive), then
* the server [...] MAY select an FFDHE cipher suite and offer an FFDHE group of its choice [...].
*/
maxBits = NamedGroup.GetMaximumFiniteFieldBits();
}
return maxBits;
}
protected virtual IList GetProtocolNames()
{
return null;
}
protected virtual bool IsSelectableCipherSuite(int cipherSuite, int availCurveBits, int availFiniteFieldBits,
IList sigAlgs)
{
// TODO[tls13] The version check should be separated out (eventually select ciphersuite before version)
return TlsUtilities.IsValidVersionForCipherSuite(cipherSuite, m_context.ServerVersion)
&& availCurveBits >= TlsEccUtilities.GetMinimumCurveBits(cipherSuite)
&& availFiniteFieldBits >= TlsDHUtilities.GetMinimumFiniteFieldBits(cipherSuite)
&& TlsUtilities.IsValidCipherSuiteForSignatureAlgorithms(cipherSuite, sigAlgs);
}
protected virtual bool PreferLocalCipherSuites()
{
return false;
}
///
protected virtual bool SelectCipherSuite(int cipherSuite)
{
this.m_selectedCipherSuite = cipherSuite;
return true;
}
protected virtual int SelectDH(int minimumFiniteFieldBits)
{
int[] clientSupportedGroups = m_context.SecurityParameters.ClientSupportedGroups;
if (clientSupportedGroups == null)
return SelectDHDefault(minimumFiniteFieldBits);
// Try to find a supported named group of the required size from the client's list.
for (int i = 0; i < clientSupportedGroups.Length; ++i)
{
int namedGroup = clientSupportedGroups[i];
if (NamedGroup.GetFiniteFieldBits(namedGroup) >= minimumFiniteFieldBits)
return namedGroup;
}
return -1;
}
protected virtual int SelectDHDefault(int minimumFiniteFieldBits)
{
return minimumFiniteFieldBits <= 2048 ? NamedGroup.ffdhe2048
: minimumFiniteFieldBits <= 3072 ? NamedGroup.ffdhe3072
: minimumFiniteFieldBits <= 4096 ? NamedGroup.ffdhe4096
: minimumFiniteFieldBits <= 6144 ? NamedGroup.ffdhe6144
: minimumFiniteFieldBits <= 8192 ? NamedGroup.ffdhe8192
: -1;
}
protected virtual int SelectECDH(int minimumCurveBits)
{
int[] clientSupportedGroups = m_context.SecurityParameters.ClientSupportedGroups;
if (clientSupportedGroups == null)
return SelectECDHDefault(minimumCurveBits);
// Try to find a supported named group of the required size from the client's list.
for (int i = 0; i < clientSupportedGroups.Length; ++i)
{
int namedGroup = clientSupportedGroups[i];
if (NamedGroup.GetCurveBits(namedGroup) >= minimumCurveBits)
return namedGroup;
}
return -1;
}
protected virtual int SelectECDHDefault(int minimumCurveBits)
{
return minimumCurveBits <= 256 ? NamedGroup.secp256r1
: minimumCurveBits <= 384 ? NamedGroup.secp384r1
: minimumCurveBits <= 521 ? NamedGroup.secp521r1
: -1;
}
protected virtual ProtocolName SelectProtocolName()
{
IList serverProtocolNames = GetProtocolNames();
if (null == serverProtocolNames || serverProtocolNames.Count < 1)
return null;
ProtocolName result = SelectProtocolName(m_clientProtocolNames, serverProtocolNames);
if (null == result)
throw new TlsFatalAlert(AlertDescription.no_application_protocol);
return result;
}
protected virtual ProtocolName SelectProtocolName(IList clientProtocolNames,
IList serverProtocolNames)
{
foreach (ProtocolName serverProtocolName in serverProtocolNames)
{
if (clientProtocolNames.Contains(serverProtocolName))
return serverProtocolName;
}
return null;
}
protected virtual bool ShouldSelectProtocolNameEarly()
{
return true;
}
protected virtual bool PreferLocalClientCertificateTypes()
{
return false;
}
protected virtual short[] GetAllowedClientCertificateTypes()
{
return null;
}
/// RFC 9146 DTLS connection ID.
///
/// This method will be called if a connection_id extension was sent by the client.
/// If the return value is non-null, the server will send this connection ID to the client to use in future packets.
/// As future communication doesn't include the connection IDs length, this should either be fixed-length
/// or include the connection ID's length. (see explanation in RFC 9146 4. "cid:")
///
/// The connection ID to use.
protected virtual byte[] GetNewConnectionID() => null;
public virtual void Init(TlsServerContext context)
{
this.m_context = context;
this.m_protocolVersions = GetSupportedVersions();
this.m_cipherSuites = GetSupportedCipherSuites();
}
public override ProtocolVersion[] GetProtocolVersions()
{
return m_protocolVersions;
}
public override int[] GetCipherSuites()
{
return m_cipherSuites;
}
public override void NotifyHandshakeBeginning()
{
base.NotifyHandshakeBeginning();
this.m_offeredCipherSuites = null;
this.m_clientExtensions = null;
this.m_encryptThenMACOffered = false;
this.m_maxFragmentLengthOffered = 0;
this.m_truncatedHMacOffered = false;
this.m_clientSentECPointFormats = false;
this.m_certificateStatusRequest = null;
this.m_selectedCipherSuite = -1;
this.m_selectedProtocolName = null;
this.m_serverExtensions.Clear();
}
public virtual TlsSession GetSessionToResume(byte[] sessionID)
{
return null;
}
public virtual byte[] GetNewSessionID()
{
return null;
}
public virtual TlsPskExternal GetExternalPsk(IList identities)
{
return null;
}
public virtual void NotifySession(TlsSession session)
{
}
public virtual void NotifyClientVersion(ProtocolVersion clientVersion)
{
}
public virtual void NotifyFallback(bool isFallback)
{
/*
* RFC 7507 3. If TLS_FALLBACK_SCSV appears in ClientHello.cipher_suites and the highest
* protocol version supported by the server is higher than the version indicated in
* ClientHello.client_version, the server MUST respond with a fatal inappropriate_fallback
* alert [..].
*/
if (isFallback)
{
ProtocolVersion[] serverVersions = GetProtocolVersions();
ProtocolVersion clientVersion = m_context.ClientVersion;
ProtocolVersion latestServerVersion;
if (clientVersion.IsTls)
{
latestServerVersion = ProtocolVersion.GetLatestTls(serverVersions);
}
else if (clientVersion.IsDtls)
{
latestServerVersion = ProtocolVersion.GetLatestDtls(serverVersions);
}
else
{
throw new TlsFatalAlert(AlertDescription.internal_error);
}
if (null != latestServerVersion && latestServerVersion.IsLaterVersionOf(clientVersion))
{
throw new TlsFatalAlert(AlertDescription.inappropriate_fallback);
}
}
}
public virtual void NotifyOfferedCipherSuites(int[] offeredCipherSuites)
{
this.m_offeredCipherSuites = offeredCipherSuites;
}
public virtual void ProcessClientExtensions(IDictionary clientExtensions)
{
this.m_clientExtensions = clientExtensions;
if (null != clientExtensions)
{
this.m_clientProtocolNames = TlsExtensionsUtilities.GetAlpnExtensionClient(clientExtensions);
if (ShouldSelectProtocolNameEarly())
{
if (null != m_clientProtocolNames && m_clientProtocolNames.Count > 0)
{
this.m_selectedProtocolName = SelectProtocolName();
}
}
// TODO[tls13] Don't need these if we have negotiated (D)TLS 1.3+
{
this.m_encryptThenMACOffered = TlsExtensionsUtilities.HasEncryptThenMacExtension(clientExtensions);
this.m_truncatedHMacOffered = TlsExtensionsUtilities.HasTruncatedHmacExtension(clientExtensions);
this.m_statusRequestV2 = TlsExtensionsUtilities.GetStatusRequestV2Extension(clientExtensions);
this.m_trustedCAKeys = TlsExtensionsUtilities.GetTrustedCAKeysExtensionClient(clientExtensions);
// We only support uncompressed format, this is just to validate the extension, and note its presence.
this.m_clientSentECPointFormats =
null != TlsExtensionsUtilities.GetSupportedPointFormatsExtension(clientExtensions);
}
this.m_certificateStatusRequest = TlsExtensionsUtilities.GetStatusRequestExtension(clientExtensions);
this.m_maxFragmentLengthOffered = TlsExtensionsUtilities.GetMaxFragmentLengthExtension(clientExtensions);
if (m_maxFragmentLengthOffered >= 0 && !MaxFragmentLength.IsValid(m_maxFragmentLengthOffered))
throw new TlsFatalAlert(AlertDescription.illegal_parameter);
}
}
public virtual ProtocolVersion GetServerVersion()
{
ProtocolVersion[] serverVersions = GetProtocolVersions();
ProtocolVersion[] clientVersions = m_context.ClientSupportedVersions;
foreach (ProtocolVersion clientVersion in clientVersions)
{
if (ProtocolVersion.Contains(serverVersions, clientVersion))
return clientVersion;
}
throw new TlsFatalAlert(AlertDescription.protocol_version);
}
public virtual int[] GetSupportedGroups()
{
// TODO[tls13] The rest of this class assumes all named groups are supported
return new int[]{ NamedGroup.x25519, NamedGroup.x448, NamedGroup.secp256r1, NamedGroup.secp384r1,
NamedGroup.ffdhe2048, NamedGroup.ffdhe3072, NamedGroup.ffdhe4096 };
}
public virtual int GetSelectedCipherSuite()
{
SecurityParameters securityParameters = m_context.SecurityParameters;
ProtocolVersion negotiatedVersion = securityParameters.NegotiatedVersion;
if (TlsUtilities.IsTlsV13(negotiatedVersion))
{
int commonCipherSuite13 = TlsUtilities.GetCommonCipherSuite13(negotiatedVersion, m_offeredCipherSuites,
GetCipherSuites(), PreferLocalCipherSuites());
if (commonCipherSuite13 >= 0 && SelectCipherSuite(commonCipherSuite13))
{
return commonCipherSuite13;
}
}
else
{
/*
* RFC 5246 7.4.3. In order to negotiate correctly, the server MUST check any candidate
* cipher suites against the "signature_algorithms" extension before selecting them. This is
* somewhat inelegant but is a compromise designed to minimize changes to the original
* cipher suite design.
*/
var sigAlgs = TlsUtilities.GetUsableSignatureAlgorithms(securityParameters.ClientSigAlgs);
/*
* RFC 4429 5.1. A server that receives a ClientHello containing one or both of these
* extensions MUST use the client's enumerated capabilities to guide its selection of an
* appropriate cipher suite. One of the proposed ECC cipher suites must be negotiated only
* if the server can successfully complete the handshake while using the curves and point
* formats supported by the client [...].
*/
int availCurveBits = GetMaximumNegotiableCurveBits();
int availFiniteFieldBits = GetMaximumNegotiableFiniteFieldBits();
int[] cipherSuites = TlsUtilities.GetCommonCipherSuites(m_offeredCipherSuites, GetCipherSuites(),
PreferLocalCipherSuites());
for (int i = 0; i < cipherSuites.Length; ++i)
{
int cipherSuite = cipherSuites[i];
if (IsSelectableCipherSuite(cipherSuite, availCurveBits, availFiniteFieldBits, sigAlgs)
&& SelectCipherSuite(cipherSuite))
{
return cipherSuite;
}
}
}
throw new TlsFatalAlert(AlertDescription.handshake_failure, GetDetailMessageNoCipherSuite());
}
// IDictionary is (Int32 -> byte[])
public virtual IDictionary GetServerExtensions()
{
bool isTlsV13 = TlsUtilities.IsTlsV13(m_context);
if (isTlsV13)
{
if (null != m_certificateStatusRequest && AllowCertificateStatus())
{
/*
* TODO[tls13] RFC 8446 4.4.2.1. OCSP Status and SCT Extensions.
*
* OCSP information is carried in an extension for a CertificateEntry.
*/
}
}
else
{
if (m_encryptThenMACOffered && AllowEncryptThenMac())
{
/*
* RFC 7366 3. If a server receives an encrypt-then-MAC request extension from a client
* and then selects a stream or Authenticated Encryption with Associated Data (AEAD)
* ciphersuite, it MUST NOT send an encrypt-then-MAC response extension back to the
* client.
*/
if (TlsUtilities.IsBlockCipherSuite(m_selectedCipherSuite))
{
TlsExtensionsUtilities.AddEncryptThenMacExtension(m_serverExtensions);
}
}
if (m_truncatedHMacOffered && AllowTruncatedHmac())
{
TlsExtensionsUtilities.AddTruncatedHmacExtension(m_serverExtensions);
}
if (m_clientSentECPointFormats && TlsEccUtilities.IsEccCipherSuite(m_selectedCipherSuite))
{
/*
* RFC 4492 5.2. A server that selects an ECC cipher suite in response to a ClientHello
* message including a Supported Point Formats Extension appends this extension (along
* with others) to its ServerHello message, enumerating the point formats it can parse.
*/
TlsExtensionsUtilities.AddSupportedPointFormatsExtension(m_serverExtensions,
new short[]{ ECPointFormat.uncompressed });
}
// TODO[tls13] See RFC 8446 4.4.2.1
if (null != m_statusRequestV2 && AllowMultiCertStatus())
{
/*
* RFC 6961 2.2. If a server returns a "CertificateStatus" message in response to a
* "status_request_v2" request, then the server MUST have included an extension of type
* "status_request_v2" with empty "extension_data" in the extended server hello..
*/
TlsExtensionsUtilities.AddEmptyExtensionData(m_serverExtensions, ExtensionType.status_request_v2);
}
else if (null != this.m_certificateStatusRequest && AllowCertificateStatus())
{
/*
* RFC 6066 8. If a server returns a "CertificateStatus" message, then the server MUST
* have included an extension of type "status_request" with empty "extension_data" in
* the extended server hello.
*/
TlsExtensionsUtilities.AddEmptyExtensionData(m_serverExtensions, ExtensionType.status_request);
}
if (null != m_trustedCAKeys && AllowTrustedCAIndication())
{
TlsExtensionsUtilities.AddTrustedCAKeysExtensionServer(m_serverExtensions);
}
}
if (m_maxFragmentLengthOffered >= 0 && MaxFragmentLength.IsValid(m_maxFragmentLengthOffered))
{
TlsExtensionsUtilities.AddMaxFragmentLengthExtension(m_serverExtensions, m_maxFragmentLengthOffered);
}
// RFC 7250 4.2 for server_certificate_type
short[] serverCertTypes = TlsExtensionsUtilities.GetServerCertificateTypeExtensionClient(
m_clientExtensions);
if (serverCertTypes != null)
{
TlsCredentials credentials = GetCredentials();
if (credentials != null)
{
// TODO An X509 certificate should still be usable as RawPublicKey
short serverCertificateType = credentials.Certificate.CertificateType;
if (CertificateType.OpenPGP == serverCertificateType && isTlsV13)
{
throw new TlsFatalAlert(AlertDescription.internal_error,
"The OpenPGP certificate type MUST NOT be used with TLS 1.3");
}
if (!Arrays.Contains(serverCertTypes, serverCertificateType))
{
// outcome 2: we support the extension but have no common types
throw new TlsFatalAlert(AlertDescription.unsupported_certificate);
}
// outcome 3: we support the extension and have a common type
TlsExtensionsUtilities.AddServerCertificateTypeExtensionServer(m_serverExtensions,
serverCertificateType);
}
}
// TODO If we won't be sending a CertificateRequest, this extension can be ignored
// RFC 7250 4.2 for client_certificate_type
short[] remoteClientCertTypes = TlsExtensionsUtilities.GetClientCertificateTypeExtensionClient(
m_clientExtensions);
if (remoteClientCertTypes != null)
{
short[] localClientCertTypes = GetAllowedClientCertificateTypes();
if (localClientCertTypes != null)
{
short[] preferredTypes;
short[] nonPreferredTypes;
if (PreferLocalClientCertificateTypes())
{
preferredTypes = localClientCertTypes;
nonPreferredTypes = remoteClientCertTypes;
}
else
{
preferredTypes = remoteClientCertTypes;
nonPreferredTypes = localClientCertTypes;
}
short selectedType = -1;
for (int i = 0; i < preferredTypes.Length; i++)
{
short preferredType = preferredTypes[i];
if (CertificateType.OpenPGP == preferredType && isTlsV13)
continue;
if (Arrays.Contains(nonPreferredTypes, preferredType))
{
selectedType = preferredType;
break;
}
}
// TODO Shouldn't be an error unless we REQUIRE client authentication
if (selectedType == -1)
{
// outcome 2: we support the extension but have no common types
throw new TlsFatalAlert(AlertDescription.unsupported_certificate);
}
// outcome 3: we support the extension and have a common type
TlsExtensionsUtilities.AddClientCertificateTypeExtensionServer(m_serverExtensions, selectedType);
} // else outcome 1: we don't support the extension
}
return m_serverExtensions;
}
public virtual void GetServerExtensionsForConnection(IDictionary serverExtensions)
{
if (!ShouldSelectProtocolNameEarly())
{
if (null != m_clientProtocolNames && m_clientProtocolNames.Count > 0)
{
this.m_selectedProtocolName = SelectProtocolName();
}
}
/*
* RFC 7301 3.1. When session resumption or session tickets [...] are used, the previous
* contents of this extension are irrelevant, and only the values in the new handshake
* messages are considered.
*/
if (null == m_selectedProtocolName)
{
serverExtensions.Remove(ExtensionType.application_layer_protocol_negotiation);
}
else
{
TlsExtensionsUtilities.AddAlpnExtensionServer(serverExtensions, m_selectedProtocolName);
}
if (ProtocolVersion.DTLSv12.Equals(m_context.ServerVersion))
{
/*
* RFC 9146 3. When a DTLS session is resumed or renegotiated, the "connection_id" extension is
* negotiated afresh.
*/
if (m_clientExtensions != null && m_clientExtensions.ContainsKey(ExtensionType.connection_id))
{
var serverConnectionID = GetNewConnectionID();
if (serverConnectionID != null)
{
TlsExtensionsUtilities.AddConnectionIDExtension(m_serverExtensions, serverConnectionID);
}
}
}
}
public virtual IList GetServerSupplementalData()
{
return null;
}
public abstract TlsCredentials GetCredentials();
public virtual CertificateStatus GetCertificateStatus()
{
return null;
}
public virtual CertificateRequest GetCertificateRequest()
{
return null;
}
public virtual TlsPskIdentityManager GetPskIdentityManager()
{
return null;
}
public virtual TlsSrpLoginParameters GetSrpLoginParameters()
{
return null;
}
public virtual TlsDHConfig GetDHConfig()
{
int minimumFiniteFieldBits = TlsDHUtilities.GetMinimumFiniteFieldBits(m_selectedCipherSuite);
int namedGroup = SelectDH(minimumFiniteFieldBits);
return TlsDHUtilities.CreateNamedDHConfig(m_context, namedGroup);
}
public virtual TlsECConfig GetECDHConfig()
{
int minimumCurveBits = TlsEccUtilities.GetMinimumCurveBits(m_selectedCipherSuite);
int namedGroup = SelectECDH(minimumCurveBits);
return TlsEccUtilities.CreateNamedECConfig(m_context, namedGroup);
}
public virtual void ProcessClientSupplementalData(IList clientSupplementalData)
{
if (clientSupplementalData != null)
throw new TlsFatalAlert(AlertDescription.unexpected_message);
}
public virtual void NotifyClientCertificate(Certificate clientCertificate)
{
throw new TlsFatalAlert(AlertDescription.internal_error);
}
public virtual NewSessionTicket GetNewSessionTicket()
{
/*
* RFC 5077 3.3. If the server determines that it does not want to include a ticket after it
* has included the SessionTicket extension in the ServerHello, then it sends a zero-length
* ticket in the NewSessionTicket handshake message.
*/
return new NewSessionTicket(0L, TlsUtilities.EmptyBytes);
}
}
}