diff --git a/README.md b/README.md index 17afc4ac..e111c853 100644 --- a/README.md +++ b/README.md @@ -12,4 +12,4 @@ The branching model (not fully in place yet) is as follows*: \* - this has been heavily influenced by http://nvie.com/posts/a-successful-git-branching-model/. -The eclipse_paho_contribution branch is what was submitted to the Eclipse Paho project in https://bugs.eclipse.org/bugs/show_bug.cgi?id=458899. +The eclipse_paho tags is what was submitted to the Eclipse Paho project in https://bugs.eclipse.org/bugs/show_bug.cgi?id=458899. diff --git a/mqtt-spy-common/.classpath b/mqtt-spy-common/.classpath index e9e64704..a9b46af7 100644 --- a/mqtt-spy-common/.classpath +++ b/mqtt-spy-common/.classpath @@ -6,6 +6,7 @@ + diff --git a/mqtt-spy-common/pom.xml b/mqtt-spy-common/pom.xml index 0a1c4139..5554f539 100644 --- a/mqtt-spy-common/pom.xml +++ b/mqtt-spy-common/pom.xml @@ -3,7 +3,7 @@ pl.baczkowicz.mqttspy mqtt-spy-common - 0.0.9 + 0.0.10 UTF-8 @@ -40,7 +40,13 @@ commons-codec 1.9 - + + + org.bouncycastle + bcprov-jdk15on + 1.52 + + diff --git a/mqtt-spy/buildNumber.properties b/mqtt-spy/buildNumber.properties index 9a70aaa0..658b4c4b 100644 --- a/mqtt-spy/buildNumber.properties +++ b/mqtt-spy/buildNumber.properties @@ -1,3 +1,3 @@ #maven.buildNumber.plugin properties file -#Sun May 31 21:03:09 BST 2015 -buildNumber=73 +#Sun May 31 21:46:28 BST 2015 +buildNumber=74 diff --git a/mqtt-spy/pom.xml b/mqtt-spy/pom.xml index 6ee31d43..e0bcf0c5 100644 --- a/mqtt-spy/pom.xml +++ b/mqtt-spy/pom.xml @@ -4,7 +4,7 @@ pl.baczkowicz.mqttspy mqtt-spy - 0.1.10 + 0.1.11-beta jar mqtt-spy @@ -38,7 +38,7 @@ pl.baczkowicz.mqttspy mqtt-spy-common - 0.0.9 + 0.0.10 maven-jaxb2-plugin diff --git a/mqtt-spy/src/main/java/pl/baczkowicz/mqttspy/connectivity/MqttAsyncConnection.java b/mqtt-spy/src/main/java/pl/baczkowicz/mqttspy/connectivity/MqttAsyncConnection.java index 8c64455e..5751d94b 100644 --- a/mqtt-spy/src/main/java/pl/baczkowicz/mqttspy/connectivity/MqttAsyncConnection.java +++ b/mqtt-spy/src/main/java/pl/baczkowicz/mqttspy/connectivity/MqttAsyncConnection.java @@ -182,10 +182,10 @@ public boolean publish(final String publicationTopic, final byte[] data, final i { try { - logger.info("Publishing message on topic \"" + publicationTopic + "\". Payload = \"" + data + "\""); + logger.info("Publishing message on topic \"" + publicationTopic + "\". Payload size = \"" + data.length + "\""); client.publish(publicationTopic, data, qos, retained); - logger.trace("Published message on topic \"" + publicationTopic + "\". Payload = \"" + data + "\""); + logger.trace("Published message on topic \"" + publicationTopic + "\". Payload size = \"" + data.length + "\""); statisticsManager.messagePublished(getId(), publicationTopic); return true; diff --git a/mqtt-spy/src/main/java/pl/baczkowicz/mqttspy/ui/controllers/edit/EditConnectionConnectivityController.java b/mqtt-spy/src/main/java/pl/baczkowicz/mqttspy/ui/controllers/edit/EditConnectionConnectivityController.java index 09d9fecd..1f017915 100644 --- a/mqtt-spy/src/main/java/pl/baczkowicz/mqttspy/ui/controllers/edit/EditConnectionConnectivityController.java +++ b/mqtt-spy/src/main/java/pl/baczkowicz/mqttspy/ui/controllers/edit/EditConnectionConnectivityController.java @@ -106,7 +106,7 @@ public void initialize(URL location, ResourceBundle resources) { @Override public void changed(ObservableValue observable, Object oldValue, Object newValue) - { + { parent.updateConnectionName(); onChange(); @@ -216,7 +216,8 @@ public UserInterfaceMqttConnectionDetails readValues(final UserInterfaceMqttConn for (final String serverURI : serverURIs) { logger.trace("Adding " + serverURI); - connection.getServerURI().add(serverURI.trim()); + // Trim and remove any prefixes - these are done dynamically based on SSL mode + connection.getServerURI().add(serverURI.trim().replaceAll(MqttUtils.TCP_PREFIX, "").replaceAll(MqttUtils.SSL_PREFIX, "")); } if (brokerAddressText.getText().endsWith(ConnectionUtils.SERVER_DELIMITER)) { diff --git a/mqtt-spy/src/main/java/pl/baczkowicz/mqttspy/ui/controllers/edit/EditConnectionSecurityController.java b/mqtt-spy/src/main/java/pl/baczkowicz/mqttspy/ui/controllers/edit/EditConnectionSecurityController.java index 256b16cf..362fe672 100644 --- a/mqtt-spy/src/main/java/pl/baczkowicz/mqttspy/ui/controllers/edit/EditConnectionSecurityController.java +++ b/mqtt-spy/src/main/java/pl/baczkowicz/mqttspy/ui/controllers/edit/EditConnectionSecurityController.java @@ -15,22 +15,49 @@ package pl.baczkowicz.mqttspy.ui.controllers.edit; import java.net.URL; +import java.security.NoSuchAlgorithmException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; import java.util.ResourceBundle; import javafx.beans.value.ChangeListener; import javafx.beans.value.ObservableValue; +import javafx.event.EventHandler; import javafx.fxml.FXML; import javafx.fxml.Initializable; +import javafx.scene.control.Button; import javafx.scene.control.CheckBox; +import javafx.scene.control.ComboBox; +import javafx.scene.control.Label; +import javafx.scene.control.ListCell; +import javafx.scene.control.ListView; import javafx.scene.control.PasswordField; import javafx.scene.control.RadioButton; +import javafx.scene.control.TableColumn; +import javafx.scene.control.TableColumn.CellEditEvent; +import javafx.scene.control.TableView; import javafx.scene.control.TextField; +import javafx.scene.control.cell.PropertyValueFactory; +import javafx.scene.control.cell.TextFieldTableCell; import javafx.scene.layout.AnchorPane; +import javafx.util.Callback; +import javafx.util.StringConverter; + +import javax.net.ssl.SSLContext; + +import pl.baczkowicz.mqttspy.common.generated.SslModeEnum; +import pl.baczkowicz.mqttspy.common.generated.SslProperty; +import pl.baczkowicz.mqttspy.common.generated.SslSettings; import pl.baczkowicz.mqttspy.common.generated.UserCredentials; import pl.baczkowicz.mqttspy.configuration.ConfiguredConnectionDetails; import pl.baczkowicz.mqttspy.configuration.generated.UserAuthenticationOptions; import pl.baczkowicz.mqttspy.configuration.generated.UserInterfaceMqttConnectionDetails; import pl.baczkowicz.mqttspy.ui.EditConnectionController; +import pl.baczkowicz.mqttspy.ui.properties.KeyValueProperty; import pl.baczkowicz.mqttspy.utils.MqttUtils; /** @@ -65,6 +92,51 @@ public class EditConnectionSecurityController extends AnchorPane implements Init @FXML private PasswordField password; + @FXML + private ComboBox modeCombo; + + @FXML + private ComboBox protocolCombo; + + @FXML + private AnchorPane customSocketFactoryPane; + + @FXML + private AnchorPane propertiesPane; + + @FXML + private TextField certificateAuthorityFile; + + @FXML + private TextField clientPassword; + + @FXML + private TextField clientKeyFile; + + @FXML + private TextField clientAuthorityFile; + + @FXML + private Label clientKeyPasswordLabel; + + @FXML + private Label clientKeyFileLabel; + + @FXML + private Label clientAuthorityFileLabel; + + @FXML + private TableView sslPropertiesTable; + + @FXML + private TableColumn propertyNameColumn; + + @FXML + private TableColumn propertyValueColumn; + + @FXML + private Button removePropertyButton; + // Other fields private final ChangeListener basicOnChangeListener = new ChangeListener() @@ -81,8 +153,8 @@ public void changed(ObservableValue observable, Object oldValue, Object newValue // =============================== public void initialize(URL location, ResourceBundle resources) - { - // Security + { + // Authentication userAuthentication.selectedProperty().addListener(new ChangeListener() { @Override @@ -99,6 +171,130 @@ public void changed(ObservableValue observable, Object oldValue, Object newValue askForPassword.selectedProperty().addListener(basicOnChangeListener); predefinedUsername.selectedProperty().addListener(basicOnChangeListener); predefinedPassword.selectedProperty().addListener(basicOnChangeListener); + + // SSL + certificateAuthorityFile.textProperty().addListener(basicOnChangeListener); + clientAuthorityFile.textProperty().addListener(basicOnChangeListener); + clientKeyFile.textProperty().addListener(basicOnChangeListener); + clientPassword.textProperty().addListener(basicOnChangeListener); + protocolCombo.getSelectionModel().selectedIndexProperty().addListener(basicOnChangeListener); + + propertyNameColumn.setCellValueFactory(new PropertyValueFactory("key")); + propertyNameColumn.setCellFactory(TextFieldTableCell.forTableColumn()); + propertyNameColumn.setOnEditCommit(new EventHandler>() + { + @Override + public void handle(CellEditEvent event) + { + KeyValueProperty p = event.getRowValue(); + String newValue = event.getNewValue(); + p.keyProperty().set(newValue); + onChange(); + } + }); + propertyValueColumn.setCellValueFactory(new PropertyValueFactory("value")); + propertyValueColumn.setCellFactory(TextFieldTableCell.forTableColumn()); + propertyValueColumn.setOnEditCommit(new EventHandler>() + { + @Override + public void handle(CellEditEvent event) + { + KeyValueProperty p = event.getRowValue(); + String newValue = event.getNewValue(); + p.valueProperty().set(newValue); + onChange(); + } + }); + + final Map modeEnumText = new HashMap<>(); + modeEnumText.put(SslModeEnum.DISABLED, "Disabled"); + modeEnumText.put(SslModeEnum.PROPERTIES, "SSL/TLS properties (using default socket factory)"); + modeEnumText.put(SslModeEnum.SERVER_ONLY, "Server authentication only (using custom socket factory)"); + modeEnumText.put(SslModeEnum.SERVER_AND_CLIENT, "Server and client authentication (using custom socket factory)"); + + try + { + final SSLContext context = SSLContext.getDefault(); + final String[] values = context.getSupportedSSLParameters().getProtocols(); + final List filteredValues = new ArrayList<>(); + filteredValues.addAll(Arrays.asList(values)); + final Iterator i = filteredValues.iterator(); + while (i.hasNext()) + { + if (i.next().contains("Hello")) + { + i.remove(); + } + } + + protocolCombo.getSelectionModel().selectedIndexProperty().addListener(basicOnChangeListener); + protocolCombo.getItems().addAll(filteredValues); + } + catch (NoSuchAlgorithmException e) + { + e.printStackTrace(); + } + + // SSL Mode + modeCombo.getSelectionModel().selectedIndexProperty().addListener(new ChangeListener() + { + @Override + public void changed(ObservableValue observable, Object oldValue, Object newValue) + { + updateSSL(); + + onChange(); + } + }); + modeCombo.setCellFactory(new Callback, ListCell>() + { + @Override + public ListCell call(ListView l) + { + return new ListCell() + { + @Override + protected void updateItem(SslModeEnum item, boolean empty) + { + super.updateItem(item, empty); + if (item == null || empty) + { + setText(null); + } + else + { + setText(modeEnumText.get(item)); + } + } + }; + } + }); + modeCombo.setConverter(new StringConverter() + { + @Override + public String toString(SslModeEnum item) + { + if (item == null) + { + return null; + } + else + { + return modeEnumText.get(item); + } + } + + @Override + public SslModeEnum fromString(String id) + { + return null; + } + }); + + for (SslModeEnum modeEnum : SslModeEnum.values()) + { + modeCombo.getItems().add(modeEnum); + } } public void init() @@ -111,9 +307,27 @@ public void init() // =============================== public void onChange() - { + { parent.onChange(); } + + public void updateSSL() + { + final boolean serverAndClient = SslModeEnum.SERVER_AND_CLIENT.equals(modeCombo.getSelectionModel().getSelectedItem()); + + propertiesPane.setVisible(SslModeEnum.PROPERTIES.equals(modeCombo.getSelectionModel().getSelectedItem())); + + customSocketFactoryPane.setVisible(serverAndClient + || SslModeEnum.SERVER_ONLY.equals(modeCombo.getSelectionModel().getSelectedItem())); + + clientPassword.setVisible(serverAndClient); + clientKeyFile.setVisible(serverAndClient); + clientAuthorityFile.setVisible(serverAndClient); + + clientKeyPasswordLabel.setVisible(serverAndClient); + clientKeyFileLabel.setVisible(serverAndClient); + clientAuthorityFileLabel.setVisible(serverAndClient); + } @Override public UserInterfaceMqttConnectionDetails readValues(final UserInterfaceMqttConnectionDetails connection) @@ -133,6 +347,33 @@ public UserInterfaceMqttConnectionDetails readValues(final UserInterfaceMqttConn connection.setUserCredentials(userCredentials); } + if (modeCombo.getSelectionModel().getSelectedItem() == null || SslModeEnum.DISABLED.equals(modeCombo.getSelectionModel().getSelectedItem())) + { + connection.setSSL(null); + } + else + { + final SslSettings sslSettings = new SslSettings(); + sslSettings.setMode(modeCombo.getSelectionModel().getSelectedItem()); + + if (SslModeEnum.PROPERTIES.equals(modeCombo.getSelectionModel().getSelectedItem())) + { + for (final KeyValueProperty property : sslPropertiesTable.getItems()) + { + sslSettings.getProperty().add(new SslProperty(property.keyProperty().getValue(), property.valueProperty().getValue())); + } + } + else + { + sslSettings.setCertificateAuthorityFile(certificateAuthorityFile.getText()); + sslSettings.setClientCertificateFile(clientAuthorityFile.getText()); + sslSettings.setClientKeyFile(clientKeyFile.getText()); + sslSettings.setClientKeyPassword(clientPassword.getText()); + sslSettings.setProtocol(protocolCombo.getSelectionModel().getSelectedItem()); + } + connection.setSSL(sslSettings); + } + return connection; } @@ -203,8 +444,66 @@ public void displayConnectionDetails(final ConfiguredConnectionDetails connectio askForPassword.setSelected(true); } + if (connection.getSSL() == null) + { + modeCombo.getSelectionModel().select(SslModeEnum.DISABLED); + } + else + { + removePropertyButton.setDisable(true); + sslPropertiesTable.getItems().clear(); + sslPropertiesTable.getSelectionModel().selectedItemProperty().addListener(new ChangeListener() + { + @Override + public void changed(ObservableValue observable, Object oldValue, Object newValue) + { + removePropertyButton.setDisable(false); + } + }); + + modeCombo.getSelectionModel().select(connection.getSSL().getMode()); + + certificateAuthorityFile.setText(connection.getSSL().getCertificateAuthorityFile()); + clientAuthorityFile.setText(connection.getSSL().getClientCertificateFile()); + clientKeyFile.setText(connection.getSSL().getClientKeyFile()); + clientPassword.setText(connection.getSSL().getClientKeyPassword()); + for (final String item : protocolCombo.getItems()) + { + if (item.equals(connection.getSSL().getProtocol())) + { + protocolCombo.getSelectionModel().select(item); + break; + } + } + + for (final SslProperty property : connection.getSSL().getProperty()) + { + sslPropertiesTable.getItems().add(new KeyValueProperty(property.getName(), property.getValue())); + } + } + updateUserAuthentication(); + updateSSL(); } + + @FXML + private void addProperty() + { + final KeyValueProperty item = new KeyValueProperty("sample.property", "sampleValue"); + sslPropertiesTable.getItems().add(item); + onChange(); + } + + @FXML + private void removeProperty() + { + final KeyValueProperty item = sslPropertiesTable.getSelectionModel().getSelectedItem(); + if (item != null) + { + sslPropertiesTable.getItems().remove(item); + onChange(); + } + } // =============================== // === Setters and getters ======= diff --git a/mqtt-spy/src/main/java/pl/baczkowicz/mqttspy/ui/properties/KeyValueProperty.java b/mqtt-spy/src/main/java/pl/baczkowicz/mqttspy/ui/properties/KeyValueProperty.java new file mode 100644 index 00000000..b31e025f --- /dev/null +++ b/mqtt-spy/src/main/java/pl/baczkowicz/mqttspy/ui/properties/KeyValueProperty.java @@ -0,0 +1,61 @@ +/*********************************************************************************** + * + * Copyright (c) 2014 Kamil Baczkowicz + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * + * Kamil Baczkowicz - initial API and implementation and/or initial documentation + * + */ +package pl.baczkowicz.mqttspy.ui.properties; + +import javafx.beans.property.SimpleStringProperty; + +/** + * Property for FX controls (e.g. table) containing a key and a value. + */ +public class KeyValueProperty +{ + /** Key as string property. */ + private SimpleStringProperty key; + + /** Value as string property. */ + private SimpleStringProperty value; + + /** + * Creates the KeyValueProperty object with the provided key and value. + * + * @param key The key to set + * @param value The value to set + */ + public KeyValueProperty(final String key, final String value) + { + this.key = new SimpleStringProperty(key); + this.value = new SimpleStringProperty(value); + } + + /** + * The key property. + * + * @return The key property as SimpleStringProperty + */ + public SimpleStringProperty keyProperty() + { + return this.key; + } + + /** + * The value property. + * + * @return The value property as SimpleStringProperty + */ + public SimpleStringProperty valueProperty() + { + return this.value; + } +} diff --git a/mqtt-spy/src/main/resources/pl/baczkowicz/mqttspy/ui/fxml/EditConnectionConnectivityPane.fxml b/mqtt-spy/src/main/resources/pl/baczkowicz/mqttspy/ui/fxml/EditConnectionConnectivityPane.fxml index 09133be8..b0ae0ddd 100644 --- a/mqtt-spy/src/main/resources/pl/baczkowicz/mqttspy/ui/fxml/EditConnectionConnectivityPane.fxml +++ b/mqtt-spy/src/main/resources/pl/baczkowicz/mqttspy/ui/fxml/EditConnectionConnectivityPane.fxml @@ -34,7 +34,7 @@ -