| package org.bouncycastle.crypto.tls; |
| |
| import java.io.IOException; |
| import java.util.Hashtable; |
| import java.util.Vector; |
| |
| import org.bouncycastle.util.Arrays; |
| |
| public abstract class AbstractTlsServer |
| extends AbstractTlsPeer |
| implements TlsServer |
| { |
| protected TlsCipherFactory cipherFactory; |
| |
| protected TlsServerContext context; |
| |
| protected ProtocolVersion clientVersion; |
| protected int[] offeredCipherSuites; |
| protected short[] offeredCompressionMethods; |
| protected Hashtable clientExtensions; |
| |
| protected boolean encryptThenMACOffered; |
| protected short maxFragmentLengthOffered; |
| protected boolean truncatedHMacOffered; |
| protected Vector supportedSignatureAlgorithms; |
| protected boolean eccCipherSuitesOffered; |
| protected int[] namedCurves; |
| protected short[] clientECPointFormats, serverECPointFormats; |
| |
| protected ProtocolVersion serverVersion; |
| protected int selectedCipherSuite; |
| protected short selectedCompressionMethod; |
| protected Hashtable serverExtensions; |
| |
| public AbstractTlsServer() |
| { |
| this(new DefaultTlsCipherFactory()); |
| } |
| |
| public AbstractTlsServer(TlsCipherFactory cipherFactory) |
| { |
| this.cipherFactory = cipherFactory; |
| } |
| |
| protected boolean allowEncryptThenMAC() |
| { |
| return true; |
| } |
| |
| protected boolean allowTruncatedHMac() |
| { |
| return false; |
| } |
| |
| protected Hashtable checkServerExtensions() |
| { |
| return this.serverExtensions = TlsExtensionsUtils.ensureExtensionsInitialised(this.serverExtensions); |
| } |
| |
| protected abstract int[] getCipherSuites(); |
| |
| protected short[] getCompressionMethods() |
| { |
| return new short[]{CompressionMethod._null}; |
| } |
| |
| protected ProtocolVersion getMaximumVersion() |
| { |
| return ProtocolVersion.TLSv11; |
| } |
| |
| protected ProtocolVersion getMinimumVersion() |
| { |
| return ProtocolVersion.TLSv10; |
| } |
| |
| protected boolean supportsClientECCCapabilities(int[] namedCurves, short[] ecPointFormats) |
| { |
| // NOTE: BC supports all the current set of point formats so we don't check them here |
| |
| if (namedCurves == null) |
| { |
| /* |
| * 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 [...]. |
| */ |
| return TlsECCUtils.hasAnySupportedNamedCurves(); |
| } |
| |
| for (int i = 0; i < namedCurves.length; ++i) |
| { |
| int namedCurve = namedCurves[i]; |
| if (NamedCurve.isValid(namedCurve) |
| && (!NamedCurve.refersToASpecificNamedCurve(namedCurve) || TlsECCUtils.isSupportedNamedCurve(namedCurve))) |
| { |
| return true; |
| } |
| } |
| |
| return false; |
| } |
| |
| public void init(TlsServerContext context) |
| { |
| this.context = context; |
| } |
| |
| public void notifyClientVersion(ProtocolVersion clientVersion) |
| throws IOException |
| { |
| this.clientVersion = clientVersion; |
| } |
| |
| public void notifyFallback(boolean isFallback) throws IOException |
| { |
| /* |
| * 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 && getMaximumVersion().isLaterVersionOf(clientVersion)) |
| { |
| throw new TlsFatalAlert(AlertDescription.inappropriate_fallback); |
| } |
| } |
| |
| public void notifyOfferedCipherSuites(int[] offeredCipherSuites) |
| throws IOException |
| { |
| this.offeredCipherSuites = offeredCipherSuites; |
| this.eccCipherSuitesOffered = TlsECCUtils.containsECCCipherSuites(this.offeredCipherSuites); |
| } |
| |
| public void notifyOfferedCompressionMethods(short[] offeredCompressionMethods) |
| throws IOException |
| { |
| this.offeredCompressionMethods = offeredCompressionMethods; |
| } |
| |
| public void processClientExtensions(Hashtable clientExtensions) |
| throws IOException |
| { |
| this.clientExtensions = clientExtensions; |
| |
| if (clientExtensions != null) |
| { |
| this.encryptThenMACOffered = TlsExtensionsUtils.hasEncryptThenMACExtension(clientExtensions); |
| |
| this.maxFragmentLengthOffered = TlsExtensionsUtils.getMaxFragmentLengthExtension(clientExtensions); |
| if (maxFragmentLengthOffered >= 0 && !MaxFragmentLength.isValid(maxFragmentLengthOffered)) |
| { |
| throw new TlsFatalAlert(AlertDescription.illegal_parameter); |
| } |
| |
| this.truncatedHMacOffered = TlsExtensionsUtils.hasTruncatedHMacExtension(clientExtensions); |
| |
| this.supportedSignatureAlgorithms = TlsUtils.getSignatureAlgorithmsExtension(clientExtensions); |
| if (this.supportedSignatureAlgorithms != null) |
| { |
| /* |
| * RFC 5246 7.4.1.4.1. Note: this extension is not meaningful for TLS versions prior |
| * to 1.2. Clients MUST NOT offer it if they are offering prior versions. |
| */ |
| if (!TlsUtils.isSignatureAlgorithmsExtensionAllowed(clientVersion)) |
| { |
| throw new TlsFatalAlert(AlertDescription.illegal_parameter); |
| } |
| } |
| |
| this.namedCurves = TlsECCUtils.getSupportedEllipticCurvesExtension(clientExtensions); |
| this.clientECPointFormats = TlsECCUtils.getSupportedPointFormatsExtension(clientExtensions); |
| } |
| |
| /* |
| * RFC 4429 4. The client MUST NOT include these extensions in the ClientHello message if it |
| * does not propose any ECC cipher suites. |
| * |
| * NOTE: This was overly strict as there may be ECC cipher suites that we don't recognize. |
| * Also, draft-ietf-tls-negotiated-ff-dhe will be overloading the 'elliptic_curves' |
| * extension to explicitly allow FFDHE (i.e. non-ECC) groups. |
| */ |
| // if (!this.eccCipherSuitesOffered && (this.namedCurves != null || this.clientECPointFormats != null)) |
| // { |
| // throw new TlsFatalAlert(AlertDescription.illegal_parameter); |
| // } |
| } |
| |
| public ProtocolVersion getServerVersion() |
| throws IOException |
| { |
| if (getMinimumVersion().isEqualOrEarlierVersionOf(clientVersion)) |
| { |
| ProtocolVersion maximumVersion = getMaximumVersion(); |
| if (clientVersion.isEqualOrEarlierVersionOf(maximumVersion)) |
| { |
| return serverVersion = clientVersion; |
| } |
| if (clientVersion.isLaterVersionOf(maximumVersion)) |
| { |
| return serverVersion = maximumVersion; |
| } |
| } |
| throw new TlsFatalAlert(AlertDescription.protocol_version); |
| } |
| |
| public int getSelectedCipherSuite() |
| throws IOException |
| { |
| /* |
| * TODO 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. |
| */ |
| |
| /* |
| * 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 [...]. |
| */ |
| boolean eccCipherSuitesEnabled = supportsClientECCCapabilities(this.namedCurves, this.clientECPointFormats); |
| |
| int[] cipherSuites = getCipherSuites(); |
| for (int i = 0; i < cipherSuites.length; ++i) |
| { |
| int cipherSuite = cipherSuites[i]; |
| |
| if (Arrays.contains(this.offeredCipherSuites, cipherSuite) |
| && (eccCipherSuitesEnabled || !TlsECCUtils.isECCCipherSuite(cipherSuite)) |
| && TlsUtils.isValidCipherSuiteForVersion(cipherSuite, serverVersion)) |
| { |
| return this.selectedCipherSuite = cipherSuite; |
| } |
| } |
| throw new TlsFatalAlert(AlertDescription.handshake_failure); |
| } |
| |
| public short getSelectedCompressionMethod() |
| throws IOException |
| { |
| short[] compressionMethods = getCompressionMethods(); |
| for (int i = 0; i < compressionMethods.length; ++i) |
| { |
| if (Arrays.contains(offeredCompressionMethods, compressionMethods[i])) |
| { |
| return this.selectedCompressionMethod = compressionMethods[i]; |
| } |
| } |
| throw new TlsFatalAlert(AlertDescription.handshake_failure); |
| } |
| |
| // Hashtable is (Integer -> byte[]) |
| public Hashtable getServerExtensions() |
| throws IOException |
| { |
| if (this.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 (TlsUtils.isBlockCipherSuite(this.selectedCipherSuite)) |
| { |
| TlsExtensionsUtils.addEncryptThenMACExtension(checkServerExtensions()); |
| } |
| } |
| |
| if (this.maxFragmentLengthOffered >= 0 && MaxFragmentLength.isValid(maxFragmentLengthOffered)) |
| { |
| TlsExtensionsUtils.addMaxFragmentLengthExtension(checkServerExtensions(), this.maxFragmentLengthOffered); |
| } |
| |
| if (this.truncatedHMacOffered && allowTruncatedHMac()) |
| { |
| TlsExtensionsUtils.addTruncatedHMacExtension(checkServerExtensions()); |
| } |
| |
| if (this.clientECPointFormats != null && TlsECCUtils.isECCCipherSuite(this.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. |
| */ |
| this.serverECPointFormats = new short[]{ ECPointFormat.uncompressed, |
| ECPointFormat.ansiX962_compressed_prime, ECPointFormat.ansiX962_compressed_char2, }; |
| |
| TlsECCUtils.addSupportedPointFormatsExtension(checkServerExtensions(), serverECPointFormats); |
| } |
| |
| return serverExtensions; |
| } |
| |
| public Vector getServerSupplementalData() |
| throws IOException |
| { |
| return null; |
| } |
| |
| public CertificateStatus getCertificateStatus() |
| throws IOException |
| { |
| return null; |
| } |
| |
| public CertificateRequest getCertificateRequest() |
| throws IOException |
| { |
| return null; |
| } |
| |
| public void processClientSupplementalData(Vector clientSupplementalData) |
| throws IOException |
| { |
| if (clientSupplementalData != null) |
| { |
| throw new TlsFatalAlert(AlertDescription.unexpected_message); |
| } |
| } |
| |
| public void notifyClientCertificate(Certificate clientCertificate) |
| throws IOException |
| { |
| throw new TlsFatalAlert(AlertDescription.internal_error); |
| } |
| |
| public TlsCompression getCompression() |
| throws IOException |
| { |
| switch (selectedCompressionMethod) |
| { |
| case CompressionMethod._null: |
| return new TlsNullCompression(); |
| |
| default: |
| /* |
| * Note: internal error here; we selected the compression method, so if we now can't |
| * produce an implementation, we shouldn't have chosen it! |
| */ |
| throw new TlsFatalAlert(AlertDescription.internal_error); |
| } |
| } |
| |
| public TlsCipher getCipher() |
| throws IOException |
| { |
| int encryptionAlgorithm = TlsUtils.getEncryptionAlgorithm(selectedCipherSuite); |
| int macAlgorithm = TlsUtils.getMACAlgorithm(selectedCipherSuite); |
| |
| return cipherFactory.createCipher(context, encryptionAlgorithm, macAlgorithm); |
| } |
| |
| public NewSessionTicket getNewSessionTicket() |
| throws IOException |
| { |
| /* |
| * 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, TlsUtils.EMPTY_BYTES); |
| } |
| } |