/*
* Copyright 2015 dc-square GmbH
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hivemq.spi.services.configuration.validation.validators;
import com.google.common.collect.ImmutableList;
import com.hivemq.spi.services.configuration.entity.Listener;
import com.hivemq.spi.services.configuration.entity.Tls;
import com.hivemq.spi.services.configuration.entity.TlsTcpListener;
import com.hivemq.spi.services.configuration.entity.TlsWebsocketListener;
import com.hivemq.spi.services.configuration.validation.ValidationError;
import com.hivemq.spi.services.configuration.validation.Validator;
import com.hivemq.spi.util.DefaultSslEngineUtil;
import com.hivemq.spi.util.SslException;
import java.io.File;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.security.Security;
import java.util.List;
/**
* The validator for listener configuration
*
* @author Dominik Obermaier
* @author Christoph Schäbel
* @since 3.0
*/
public class ListenerValidator implements Validator<Listener> {
@Override
public List<ValidationError> validate(final Listener listener, final String name) {
final ImmutableList.Builder<ValidationError> validationErrors = ImmutableList.builder();
validatePort(listener.getPort(), validationErrors);
if (listener instanceof TlsWebsocketListener) {
final Tls tls = ((TlsWebsocketListener) listener).getTls();
validateTls(tls, validationErrors);
} else if (listener instanceof TlsTcpListener) {
final Tls tls = ((TlsTcpListener) listener).getTls();
validateTls(tls, validationErrors);
}
return validationErrors.build();
}
private void validateTls(final Tls tls, final ImmutableList.Builder<ValidationError> validationErrors) {
validateHandshake(tls, validationErrors);
validateKeyStoreType(tls, validationErrors);
validateTrustStoreType(tls, validationErrors);
validateKeyStorePath(tls, validationErrors);
validateTrustStorePath(tls, validationErrors);
validateCipherSuites(tls, validationErrors);
validateProtocols(tls, validationErrors);
}
private void validatePort(final Integer port, final ImmutableList.Builder<ValidationError> validationErrors) {
if (port < 1 || port > 65535) {
validationErrors.add(new ValidationError("%d is a invalid port. A valid port must have a value between 1 and 65535.", port));
}
}
private void validateHandshake(final Tls tls, final ImmutableList.Builder<ValidationError> builder) {
final Integer handshakeValue = tls.getHandshakeTimeout();
if (handshakeValue < 0) {
final ValidationError handshakeValidationError = new ValidationError("%d is a invalid handshake timeout. A valid handshake timeout must be >= 0", handshakeValue);
builder.add(handshakeValidationError);
}
}
private void validateKeyStoreType(final Tls tls, final ImmutableList.Builder<ValidationError> builder) {
if (!Security.getAlgorithms("KeyStore").contains(tls.getKeystoreType())) {
final ValidationError keystoreTypeValidationError = new ValidationError("Keystore Type '%s' is not supported", tls.getKeystoreType());
builder.add(keystoreTypeValidationError);
}
}
private void validateTrustStoreType(final Tls tls, final ImmutableList.Builder<ValidationError> builder) {
if (!Security.getAlgorithms("KeyStore").contains(tls.getTruststoreType())) {
final ValidationError truststoreTypeValidationError = new ValidationError("Truststore Type '%s' is not supported", tls.getTruststoreType());
builder.add(truststoreTypeValidationError);
}
}
private void validateKeyStorePath(final Tls tls, final ImmutableList.Builder<ValidationError> builder) {
if (tls.getKeystorePath().trim().length() > 0) {
File keystoreFile = new File(tls.getKeystorePath());
if (!keystoreFile.exists()) {
final ValidationError validationError = new ValidationError("Keystore file '%s' does not exist", tls.getKeystorePath());
builder.add(validationError);
return;
}
if (!Files.isReadable(FileSystems.getDefault().getPath(keystoreFile.getAbsolutePath()))) {
final ValidationError validationError = new ValidationError("Keystore file '%s' is not readable, please check file permissions", tls.getKeystorePath());
builder.add(validationError);
}
}
}
private void validateTrustStorePath(final Tls tls, final ImmutableList.Builder<ValidationError> builder) {
if (tls.getTruststorePath().trim().length() > 0) {
File truststoreFile = new File(tls.getTruststorePath());
if (!truststoreFile.exists()) {
final ValidationError validationError = new ValidationError("Truststore file '%s' does not exist", tls.getTruststorePath());
builder.add(validationError);
return;
}
//Do not use if (!truststoreFile.canRead()) { // because it seems to be buggy on windows
if (!Files.isReadable(FileSystems.getDefault().getPath(truststoreFile.getAbsolutePath()))) {
final ValidationError validationError = new ValidationError("Truststore file '%s' is not readable, please check file permissions", tls.getTruststorePath());
builder.add(validationError);
}
}
}
private void validateProtocols(final Tls tls, final ImmutableList.Builder<ValidationError> builder) {
try {
final List<String> tlsProtocols = tls.getProtocols();
final List<String> supportedProtocols = new DefaultSslEngineUtil().getSupportedProtocols();
checkSupportedList(supportedProtocols,
tlsProtocols,
builder,
"the protocol '%s' is not supported by this JVM",
"None of the chosen TLS protocols is supported by this JVM");
} catch (SslException e) {
final ValidationError validationError = new ValidationError(e.getMessage());
builder.add(validationError);
}
}
private void validateCipherSuites(final Tls tls, final ImmutableList.Builder<ValidationError> builder) {
try {
final List<String> tlsCipherSuites = tls.getCipherSuites();
final List<String> supportedCipherSuites = new DefaultSslEngineUtil().getSupportedCipherSuites();
checkSupportedList(supportedCipherSuites,
tlsCipherSuites,
builder,
"the cipher suite '%s' is not supported by this JVM",
"None of the chosen TLS cipher suites is supported by this JVM");
} catch (SslException e) {
final ValidationError validationError = new ValidationError(e.getMessage());
builder.add(validationError);
}
}
private void checkSupportedList(final List supportedElements,
final List chosenElements,
final ImmutableList.Builder<ValidationError> builder,
final String warnText,
final String errorText) {
int supportedCount = 0;
if (chosenElements.size() < 1) {
return;
}
for (Object element : chosenElements) {
if (supportedElements.contains(element)) {
supportedCount++;
} else {
final ValidationError validationError = new ValidationError(warnText, element.toString());
builder.add(validationError);
}
}
if (supportedCount == 0) {
final ValidationError validationError = new ValidationError(errorText);
builder.add(validationError);
}
}
}