@ -0,0 +1 @@ | |||||
If you find software that doesn’t have a license, that means you have no permission from the creators of the software to use, modify, or share the software. Although a code host such as GitHub may allow you to view and fork the code, this does not imply that you are permitted to use, modify, or share the software for any purpose. |
@ -0,0 +1,64 @@ | |||||
# CommandPC | |||||
CommandPC is a program for executing commands on a remote machine regardless of the operating system. | |||||
## Description | |||||
The too long didn't read version is that: it's like SSH, but less secure and just a fun experimentation with threads, internationalization, encryption and GUI in JAVA. | |||||
Composed of two part, a client and a server, this application allow you to send encrypted command from one machine to another. Both the client and the server can operate with or without a gui. | |||||
English and French language are disponible within the application. | |||||
## Instruction | |||||
### Server | |||||
Without the `nogui` argument, the server display its status with a system tray icon. By right-clicking the icon you can interact with it. | |||||
| Server started | Server stopped | | |||||
|:--------------:|:--------------:| | |||||
|![server-systemTray-icon](img/CommandPC_systemTrayIcon.png)|![server-systemTray-icon](img/CommandPC_systemTrayIcon2.png)||| | |||||
Example: | |||||
- Windows sytem tray: ![server-systemTray-icon](img/CommandPC_systemTrayWindows.png) | |||||
- Linux XFCE system tray: ![server-systemTray-icon](img/CommandPC_systemTrayLinux.png) | |||||
Right-clicking on the icon allow you to start and stop the server. The command line argument `port=X` chand the listening port. | |||||
### Client | |||||
The client use a graphical interface to guide the user and is available in both French and English (using the "Français"/"English" button at the bottom) | |||||
The interface is composed of two pane: "connection" and "command". | |||||
The "connection" pane allows the connection to a remote server. | |||||
![connection-result](img/CommandPC_connectionResult.png) | |||||
After the connection established, the "command" pane allow to send commands to the server. | |||||
| 'ls /' on a Linux machine | 'ipconfig' on a Windows machine and gui in French for a change | | |||||
|:-------------------------:|:-------------------------------:| | |||||
|![ls](img/CommandPC_commandLs.png)|![ipconfig](img/CommandPC_commandIpconfig.png)| | |||||
## Security concerns | |||||
**TLDR: Use at your own risk over a secure network.** | |||||
### Command | |||||
No effort is made to check if the command that you send is compatible with the user privilige, exist or is even safe to use. | |||||
### Identity | |||||
No identification of the user or the machine is made. In other words, if a server is running any number of client can connect and run command on it. | |||||
### Encryption | |||||
This software use AES 128 bits encryption, but like too many software (including payed one) it has flaws that make it unsuitable to use over unsecured networks. | |||||
The main issue is the fact that during the first communication between the client and the server, the communication is encrypted via a master key. | |||||
This master key, is the same for all firsts exchanges. After that, a new communication key is generated between individual client and the server. | |||||
If somebody were to use a packet sniffer on the network, knowing the master key it's possible to get the communication key thus defeating the encryption entirely. | |||||
Side note, AES 128 allow this program to run on any implementation of the Java platform. See the [Cipher documentation](https://docs.oracle.com/javase/8/docs/api/javax/crypto/Cipher.html) for the complete list of "safe to use" cipher standard. |
@ -0,0 +1,11 @@ | |||||
<?xml version="1.0" encoding="UTF-8"?> | |||||
<classpath> | |||||
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-15"> | |||||
<attributes> | |||||
<attribute name="module" value="true"/> | |||||
</attributes> | |||||
</classpathentry> | |||||
<classpathentry excluding="gui/G.java|gui/PanelCommunication2.java|gui/PanelConnection2.java|thread/" kind="src" path="src"/> | |||||
<classpathentry kind="src" path="resource"/> | |||||
<classpathentry kind="output" path="bin"/> | |||||
</classpath> |
@ -0,0 +1,91 @@ | |||||
######################## | |||||
#JAVA | |||||
######################## | |||||
# Compiled class file | |||||
*.class | |||||
# Log file | |||||
*.log | |||||
# BlueJ files | |||||
*.ctxt | |||||
# Mobile Tools for Java (J2ME) | |||||
.mtj.tmp/ | |||||
# Package Files # | |||||
*.jar | |||||
*.war | |||||
*.nar | |||||
*.ear | |||||
*.zip | |||||
*.tar.gz | |||||
*.rar | |||||
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml | |||||
hs_err_pid* | |||||
######################## | |||||
# ECLIPSE | |||||
######################## | |||||
.metadata | |||||
bin/ | |||||
tmp/ | |||||
*.tmp | |||||
*.bak | |||||
*.swp | |||||
*~.nib | |||||
local.properties | |||||
.settings/ | |||||
.loadpath | |||||
.recommenders | |||||
# External tool builders | |||||
.externalToolBuilders/ | |||||
# Locally stored "Eclipse launch configurations" | |||||
*.launch | |||||
# PyDev specific (Python IDE for Eclipse) | |||||
*.pydevproject | |||||
# CDT-specific (C/C++ Development Tooling) | |||||
.cproject | |||||
# CDT- autotools | |||||
.autotools | |||||
# Java annotation processor (APT) | |||||
.factorypath | |||||
# PDT-specific (PHP Development Tools) | |||||
.buildpath | |||||
# sbteclipse plugin | |||||
.target | |||||
# Tern plugin | |||||
.tern-project | |||||
# TeXlipse plugin | |||||
.texlipse | |||||
# STS (Spring Tool Suite) | |||||
.springBeans | |||||
# Code Recommenders | |||||
.recommenders/ | |||||
# Annotation Processing | |||||
.apt_generated/ | |||||
.apt_generated_test/ | |||||
# Scala IDE specific (Scala & Java development for Eclipse) | |||||
.cache-main | |||||
.scala_dependencies | |||||
.worksheet | |||||
# Uncomment this line if you wish to ignore the project description file. | |||||
# Typically, this file would be tracked if it contains build/dependency configurations: | |||||
#.project | |||||
@ -0,0 +1,17 @@ | |||||
<?xml version="1.0" encoding="UTF-8"?> | |||||
<projectDescription> | |||||
<name>client</name> | |||||
<comment></comment> | |||||
<projects> | |||||
</projects> | |||||
<buildSpec> | |||||
<buildCommand> | |||||
<name>org.eclipse.jdt.core.javabuilder</name> | |||||
<arguments> | |||||
</arguments> | |||||
</buildCommand> | |||||
</buildSpec> | |||||
<natures> | |||||
<nature>org.eclipse.jdt.core.javanature</nature> | |||||
</natures> | |||||
</projectDescription> |
@ -0,0 +1,38 @@ | |||||
address = Address | |||||
clear = Clear | |||||
command = Command | |||||
commandToExecute = Command to execute: | |||||
connected = Connected | |||||
connectedTo = Connected to | |||||
connection = Connection | |||||
connectionEnded = Connection ended | |||||
errorSpecifyAddressAndPort = Specify both address and port. | |||||
execute = Execute | |||||
help_argument = Arguments: | |||||
help_cmd = Send a command X to the remote server. Don't launch the graphical user interface. | |||||
help_help = Show this help message. | |||||
help_intro = CommandPC is a program for executing commands on a remote machine regardless of the operating system. | |||||
help_ip = Set the server address to X. | |||||
help_port = Set the server port to X. | |||||
languageAlt = Fran\u00E7ais | |||||
languageName = English | |||||
port = Port |
@ -0,0 +1,38 @@ | |||||
address = Adresse | |||||
clear = Vider | |||||
command = Commande | |||||
commandToExecute = Commande \u00E0 ex\u00E9cuter: | |||||
connected = Connect\u00E9 | |||||
connectedTo = Connect\u00E9 \u00E0 | |||||
connection = Connexion | |||||
connectionEnded = Connexion termin\u00E9e | |||||
errorSpecifyAddressAndPort = Pr\u00E9cisez l'adresse et le port. | |||||
execute = Ex\u00E9cuter | |||||
help_argument = Arguments: | |||||
help_cmd = Envoie une commande X au serveur distant. Ne d\u00E9marre pas l'interface graphique. | |||||
help_help = Affiche ce message d'aide. | |||||
help_intro = CommandPC est un programme destin\u00E9 \u00E0 ex\u00E9cuter des commandes sur un serveur distant, quelque soit son syst\u00E8me d'exploitation. | |||||
help_ip = D\u00E9fini l'adresse du serveur distant. | |||||
help_port = D\u00E9fini le port du serveur distant. | |||||
languageAlt = English | |||||
languageName = Fran\u00E7ais | |||||
port = Port |
@ -0,0 +1,53 @@ | |||||
package generic; | |||||
/** | |||||
* Generic response class. | |||||
*/ | |||||
public class Status<T,E> { | |||||
/** Indicate if the operation was a success. By default, <code>false</code>. */ | |||||
public boolean success; | |||||
/** Response message.*/ | |||||
public String message; | |||||
/** Response payload.*/ | |||||
public T payload; | |||||
/** Response error */ | |||||
public E error; | |||||
public Status() { | |||||
super(); | |||||
this.success=false; | |||||
} | |||||
public Status(boolean success) { | |||||
super(); | |||||
this.success = success; | |||||
} | |||||
public Status(boolean success, String message) { | |||||
super(); | |||||
this.success = success; | |||||
this.message = message; | |||||
} | |||||
public Status(boolean success, String message, T payload) { | |||||
super(); | |||||
this.success = success; | |||||
this.message = message; | |||||
this.payload = payload; | |||||
} | |||||
public Status(boolean success, String message, T payload, E error) { | |||||
super(); | |||||
this.success = success; | |||||
this.message = message; | |||||
this.payload = payload; | |||||
this.error = error; | |||||
} | |||||
@Override | |||||
public String toString() { | |||||
String result="Status [success="+success+", message="+message; | |||||
if(payload!=null) { | |||||
result+=", payload="+payload.toString(); | |||||
} | |||||
if(error!=null) { | |||||
result+=", error="+error.toString(); | |||||
} | |||||
return result+"]"; | |||||
} | |||||
} |
@ -0,0 +1,173 @@ | |||||
package gui; | |||||
import java.awt.GridBagConstraints; | |||||
import java.awt.GridBagLayout; | |||||
import java.util.Locale; | |||||
import java.util.ResourceBundle; | |||||
import javax.swing.ImageIcon; | |||||
import javax.swing.JButton; | |||||
import javax.swing.JFrame; | |||||
import javax.swing.JPanel; | |||||
import javax.swing.JTabbedPane; | |||||
import javax.swing.SwingConstants; | |||||
import javax.swing.WindowConstants; | |||||
import generic.Status; | |||||
import gui.panel.CommunicationPanel; | |||||
import gui.panel.ConnectionPanel; | |||||
import network.CommunicationManager; | |||||
public class GUI extends JFrame { | |||||
private static final long serialVersionUID = 1L; | |||||
private ConnectionPanel connectionPanel; | |||||
private CommunicationPanel communicationPanel; | |||||
private JPanel languagePanel; | |||||
private JButton languageButton; | |||||
private JTabbedPane tabbedPane; | |||||
private CommunicationManager communicationManager; | |||||
public GUI(CommunicationManager communicationManager) { | |||||
super(); | |||||
this.communicationManager=communicationManager; | |||||
connectionPanel=new ConnectionPanel(this); | |||||
connectionPanel.setAddress(communicationManager.getAddress()); | |||||
connectionPanel.setPort(String.valueOf(communicationManager.getPort())); | |||||
communicationPanel=new CommunicationPanel(this); | |||||
initialize(); | |||||
} | |||||
/** | |||||
* Initialize the default state of the different GUI elements. | |||||
*/ | |||||
private void initialize() { | |||||
this.setTitle("CommandPC - MARTIN Romain"); | |||||
this.setIconImage(getToolkit().getImage(getClass().getResource("/iconCommandPC.png"))); | |||||
this.setSize(600,300); | |||||
this.setResizable(true); | |||||
this.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE); | |||||
this.addWindowListener(new java.awt.event.WindowAdapter() { | |||||
@Override | |||||
public void windowClosing(java.awt.event.WindowEvent e) { | |||||
communicationManager.disconnect(); | |||||
dispose(); | |||||
}; | |||||
}); | |||||
GridBagLayout gbl_contentPane=new GridBagLayout(); | |||||
getContentPane().setLayout(gbl_contentPane); | |||||
tabbedPane=new JTabbedPane(SwingConstants.TOP); | |||||
tabbedPane.add("panelConnection",connectionPanel); | |||||
tabbedPane.add("panelCommunication",communicationPanel); | |||||
tabbedPane.setIconAt(0,new ImageIcon(getClass().getResource("/iconConnection.png"))); | |||||
tabbedPane.setIconAt(1,new ImageIcon(getClass().getResource("/iconCommunication.png"))); | |||||
tabbedPane.setEnabledAt(1,false); | |||||
GridBagConstraints gbc_tabbedPane=new GridBagConstraints(); | |||||
gbc_tabbedPane.gridx=0; | |||||
gbc_tabbedPane.gridy=0; | |||||
gbc_tabbedPane.weightx=1.0d; | |||||
gbc_tabbedPane.weighty=1.0d; | |||||
gbc_tabbedPane.fill=GridBagConstraints.BOTH; | |||||
getContentPane().add(tabbedPane,gbc_tabbedPane); | |||||
GridBagLayout gbl_languagePanel=new GridBagLayout(); | |||||
languagePanel=new JPanel(gbl_languagePanel); | |||||
GridBagConstraints gbc_languagePanel=new GridBagConstraints(); | |||||
gbc_languagePanel.gridx=0; | |||||
gbc_languagePanel.gridy=1; | |||||
gbc_languagePanel.fill=GridBagConstraints.HORIZONTAL; | |||||
languageButton=new JButton("languageButton"); | |||||
languageButton.addActionListener(new java.awt.event.ActionListener() { | |||||
@Override | |||||
public void actionPerformed(java.awt.event.ActionEvent e) { | |||||
if(Locale.getDefault().getLanguage().equals(Locale.FRENCH.toString())) { | |||||
Locale.setDefault(Locale.ENGLISH); | |||||
}else { | |||||
Locale.setDefault(Locale.FRENCH); | |||||
} | |||||
//ResourceBundle.clearCache(); | |||||
setText(); | |||||
} | |||||
}); | |||||
languagePanel.add(languageButton); | |||||
getContentPane().add(languagePanel,gbc_languagePanel); | |||||
setText(); | |||||
} | |||||
/** | |||||
* Set the correct text for all graphical elements according to the default {@link Locale}. | |||||
* @see java.util.Locale#getDefault() | |||||
*/ | |||||
private void setText(){ | |||||
ResourceBundle resourceBundle=ResourceBundle.getBundle("bundle/Bundle",Locale.getDefault()); | |||||
languageButton.setText(resourceBundle.getString("languageAlt")); | |||||
tabbedPane.setTitleAt(0,resourceBundle.getString("connection")); | |||||
tabbedPane.setTitleAt(1,resourceBundle.getString("command")); | |||||
communicationPanel.setText(); | |||||
connectionPanel.setText(); | |||||
} | |||||
/** | |||||
* Request connection status change. | |||||
* @param isConnection true if this is a connection request, false to disconnect. | |||||
* @param address address of the remote server. | |||||
* @param port port of the remote server. | |||||
* @return true if operation successful. | |||||
*/ | |||||
public boolean changeConnectionStatus(boolean isConnection,String address,String port) { | |||||
//Disconnection | |||||
if(isConnection==false) { | |||||
communicationManager.disconnect(); | |||||
tabbedPane.setEnabledAt(1,false); | |||||
tabbedPane.setSelectedIndex(0); | |||||
return true; | |||||
} | |||||
ResourceBundle resourceBundle=ResourceBundle.getBundle("bundle/Bundle",Locale.getDefault()); | |||||
if(address.isBlank() || port.isBlank()) { | |||||
connectionPanel.addLog(resourceBundle.getString("errorSpecifyAddressAndPort"),true); | |||||
return false; | |||||
} | |||||
Status<Object,Exception> result=communicationManager.connect(address,Integer.valueOf(port)); | |||||
if(result.success) { | |||||
connectionPanel.addLog(resourceBundle.getString("connectedTo")+" "+result.message.toString(),false); | |||||
tabbedPane.setEnabledAt(1,true); | |||||
tabbedPane.setSelectedIndex(1); | |||||
} else { | |||||
connectionPanel.addLog(result.error.toString(),true); | |||||
} | |||||
return result.success; | |||||
} | |||||
/** | |||||
* Send command to the server and receive his answer. | |||||
* @param text command to send. | |||||
*/ | |||||
public void sendCommand(String text) { | |||||
Status<Object,Exception> resultSend=communicationManager.encryptAndSendString(text); | |||||
if(resultSend.success==false) { | |||||
communicationPanel.addLog(resultSend.error.toString(), true); | |||||
return; | |||||
} | |||||
Status<String, Exception> resultReceive=communicationManager.receiveAndDecryptString(); | |||||
if(resultReceive.success) { | |||||
if(resultReceive.payload.trim().startsWith("java")) { | |||||
communicationPanel.addLog(resultReceive.payload.trim(), true); | |||||
} else { | |||||
communicationPanel.addLog(resultReceive.payload.trim(), false); | |||||
} | |||||
} else { | |||||
communicationPanel.addLog(resultReceive.error.toString(), true); | |||||
} | |||||
} | |||||
} |
@ -0,0 +1,85 @@ | |||||
package gui.panel; | |||||
import java.awt.GridBagConstraints; | |||||
import java.awt.event.KeyEvent; | |||||
import java.util.Locale; | |||||
import java.util.ResourceBundle; | |||||
import javax.swing.JButton; | |||||
import javax.swing.JLabel; | |||||
import javax.swing.JTextField; | |||||
import gui.GUI; | |||||
/** | |||||
* <p>Handle the communication with the server</p> | |||||
* <p>Allow the user to input and send commands and display their results.</p> | |||||
*/ | |||||
public class CommunicationPanel extends LogPanel { | |||||
private static final long serialVersionUID = 1L; | |||||
private JLabel commandLabel; | |||||
private JTextField commandText; | |||||
private JButton executeButton; | |||||
private GUI gui; | |||||
public CommunicationPanel(GUI gui) { | |||||
super(); | |||||
this.gui=gui; | |||||
initialize(); | |||||
} | |||||
@Override | |||||
protected void initialize() { | |||||
super.initialize(); | |||||
//GridBagLayout gbl_main=new GridBagLayout(); | |||||
//this.setLayout(gbl_main); | |||||
commandLabel=new JLabel("labelCommand"); | |||||
GridBagConstraints gbc_commandLabel=new GridBagConstraints(); | |||||
gbc_commandLabel.gridx=0; | |||||
gbc_commandLabel.gridy=0; | |||||
gbc_commandLabel.anchor=GridBagConstraints.WEST; | |||||
this.add(commandLabel,gbc_commandLabel); | |||||
commandText=new JTextField(); | |||||
commandText.addKeyListener(new java.awt.event.KeyAdapter() { | |||||
@Override | |||||
public void keyPressed(java.awt.event.KeyEvent e) { | |||||
if(e.getKeyCode() == KeyEvent.VK_ENTER){ | |||||
executeButton.doClick(); | |||||
} | |||||
} | |||||
}); | |||||
GridBagConstraints gbc_commandText=new GridBagConstraints(); | |||||
gbc_commandText.gridx=0; | |||||
gbc_commandText.gridy=1; | |||||
gbc_commandText.weightx=1.0d; | |||||
gbc_commandText.fill=GridBagConstraints.BOTH; | |||||
this.add(commandText,gbc_commandText); | |||||
executeButton=new JButton("executeButton"); | |||||
executeButton.addActionListener(new java.awt.event.ActionListener() { | |||||
@Override | |||||
public void actionPerformed(java.awt.event.ActionEvent e) { | |||||
if(commandText.getText().isBlank()==false) { | |||||
gui.sendCommand(commandText.getText()); | |||||
} | |||||
} | |||||
}); | |||||
GridBagConstraints gbc_executeButton=new GridBagConstraints(); | |||||
gbc_executeButton.gridx=3; | |||||
gbc_executeButton.gridy=1; | |||||
this.add(executeButton,gbc_executeButton); | |||||
setText(); | |||||
} | |||||
@Override | |||||
public void setText() { | |||||
super.setText(); | |||||
ResourceBundle resourceBundle=ResourceBundle.getBundle("bundle/Bundle",Locale.getDefault()); | |||||
commandLabel.setText(resourceBundle.getString("commandToExecute")); | |||||
executeButton.setText(resourceBundle.getString("execute")); | |||||
} | |||||
} |
@ -0,0 +1,145 @@ | |||||
package gui.panel; | |||||
import java.awt.GridBagConstraints; | |||||
import java.awt.event.KeyEvent; | |||||
import java.util.Locale; | |||||
import java.util.ResourceBundle; | |||||
import javax.swing.JLabel; | |||||
import javax.swing.JTextField; | |||||
import javax.swing.JToggleButton; | |||||
import javax.swing.SwingConstants; | |||||
import gui.GUI; | |||||
/** | |||||
* Handle connection state, allowing to set up and establish a connection as well as displaying it status. | |||||
*/ | |||||
public class ConnectionPanel extends LogPanel { | |||||
private static final long serialVersionUID = 1L; | |||||
private JLabel addressLabel; | |||||
private JTextField addressText; | |||||
private JLabel portLabel; | |||||
private JTextField portText; | |||||
private JLabel addressPortSepartatorLabel; | |||||
private JToggleButton connectionToggleButton; | |||||
private GUI gui; | |||||
public void setAddress(String address) { | |||||
if(addressText!=null) addressText.setText(address); | |||||
} | |||||
public void setPort(String port) { | |||||
if(portText!=null) portText.setText(port); | |||||
} | |||||
public ConnectionPanel(GUI gui) { | |||||
super(); | |||||
this.gui=gui; | |||||
initialize(); | |||||
} | |||||
@Override | |||||
protected void initialize() { | |||||
super.initialize(); | |||||
/*GridBagLayout gbl_main=new GridBagLayout(); | |||||
this.setLayout(gbl_main);*/ | |||||
addressLabel=new JLabel("addressLabel"); | |||||
GridBagConstraints gbc_addressLabel=new GridBagConstraints(); | |||||
gbc_addressLabel.gridx=0; | |||||
gbc_addressLabel.gridy=0; | |||||
this.add(addressLabel,gbc_addressLabel); | |||||
addressText=new JTextField(); | |||||
addressText.setHorizontalAlignment(SwingConstants.CENTER); | |||||
addressText.addKeyListener(new java.awt.event.KeyAdapter() { | |||||
@Override | |||||
public void keyPressed(java.awt.event.KeyEvent e) { | |||||
if(e.getKeyCode() == KeyEvent.VK_ENTER){ | |||||
if(connectionToggleButton.isSelected()==false) { | |||||
connectionToggleButton.doClick(); | |||||
} | |||||
} | |||||
} | |||||
}); | |||||
GridBagConstraints gbc_addressText=new GridBagConstraints(); | |||||
gbc_addressText.gridx=0; | |||||
gbc_addressText.gridy=1; | |||||
gbc_addressText.weightx=0.5d; | |||||
gbc_addressText.fill=GridBagConstraints.BOTH; | |||||
this.add(addressText,gbc_addressText); | |||||
addressPortSepartatorLabel=new JLabel(" : "); | |||||
addressPortSepartatorLabel.setHorizontalAlignment(SwingConstants.CENTER); | |||||
GridBagConstraints gbc_addressPortSepartatorLabel=new GridBagConstraints(); | |||||
gbc_addressPortSepartatorLabel.gridx=1; | |||||
gbc_addressPortSepartatorLabel.gridy=1; | |||||
this.add(addressPortSepartatorLabel,gbc_addressPortSepartatorLabel); | |||||
portLabel=new JLabel("portLabel"); | |||||
GridBagConstraints gbc_portLabel=new GridBagConstraints(); | |||||
gbc_portLabel.gridx=2; | |||||
gbc_portLabel.gridy=0; | |||||
this.add(portLabel,gbc_portLabel); | |||||
portText=new JTextField(); | |||||
portText.setHorizontalAlignment(SwingConstants.CENTER); | |||||
portText.addKeyListener(new java.awt.event.KeyAdapter() { | |||||
@Override | |||||
public void keyPressed(java.awt.event.KeyEvent e) { | |||||
if(e.getKeyCode() == KeyEvent.VK_ENTER){ | |||||
if(connectionToggleButton.isSelected()==false) { | |||||
connectionToggleButton.doClick(); | |||||
} | |||||
} | |||||
} | |||||
}); | |||||
GridBagConstraints gbc_portText=new GridBagConstraints(); | |||||
gbc_portText.gridx=2; | |||||
gbc_portText.gridy=1; | |||||
gbc_portText.weightx=0.5d; | |||||
gbc_portText.fill=GridBagConstraints.BOTH; | |||||
this.add(portText,gbc_portText); | |||||
connectionToggleButton=new JToggleButton("connectionToggleButton"); | |||||
connectionToggleButton.addActionListener(new java.awt.event.ActionListener() { | |||||
@Override | |||||
public void actionPerformed(java.awt.event.ActionEvent e) { | |||||
boolean success=gui.changeConnectionStatus(connectionToggleButton.isSelected(),addressText.getText(),portText.getText()); | |||||
if(success==false) { | |||||
connectionToggleButton.setSelected(false); | |||||
} | |||||
setTextConnectionToggleButton(); | |||||
} | |||||
}); | |||||
GridBagConstraints gbc_connectionToggleButton=new GridBagConstraints(); | |||||
gbc_connectionToggleButton.gridx=3; | |||||
gbc_connectionToggleButton.gridy=1; | |||||
gbc_connectionToggleButton.fill=GridBagConstraints.HORIZONTAL; | |||||
this.add(connectionToggleButton,gbc_connectionToggleButton); | |||||
setText(); | |||||
} | |||||
/** | |||||
* Set the correct text for toggleButtonConnection regarding the selected language and it's status. | |||||
*/ | |||||
private void setTextConnectionToggleButton() { | |||||
ResourceBundle resourceBundle=ResourceBundle.getBundle("bundle/Bundle",Locale.getDefault()); | |||||
if(connectionToggleButton.isSelected()) { | |||||
connectionToggleButton.setText(resourceBundle.getString("connected")); | |||||
}else { | |||||
connectionToggleButton.setText(resourceBundle.getString("connection")); | |||||
} | |||||
} | |||||
@Override | |||||
public void setText() { | |||||
super.setText(); | |||||
ResourceBundle resourceBundle=ResourceBundle.getBundle("bundle/Bundle",Locale.getDefault()); | |||||
addressLabel.setText(resourceBundle.getString("address")); | |||||
portLabel.setText(resourceBundle.getString("port")); | |||||
setTextConnectionToggleButton(); | |||||
} | |||||
} |
@ -0,0 +1,97 @@ | |||||
package gui.panel; | |||||
import java.awt.Color; | |||||
import java.awt.GridBagConstraints; | |||||
import java.awt.GridBagLayout; | |||||
import java.awt.Insets; | |||||
import java.util.Locale; | |||||
import java.util.ResourceBundle; | |||||
import javax.swing.JButton; | |||||
import javax.swing.JPanel; | |||||
import javax.swing.JScrollPane; | |||||
import javax.swing.JTextPane; | |||||
import javax.swing.text.BadLocationException; | |||||
import javax.swing.text.SimpleAttributeSet; | |||||
import javax.swing.text.StyleConstants; | |||||
import javax.swing.text.StyledDocument; | |||||
public class LogPanel extends JPanel { | |||||
private static final long serialVersionUID = 1L; | |||||
private JTextPane logTextPane; | |||||
private StyledDocument logStyleDocument; | |||||
private SimpleAttributeSet normalAttributeSet; | |||||
private SimpleAttributeSet errorAttributeSet; | |||||
private JScrollPane logScrollPane; | |||||
private JButton clearLogButton; | |||||
public LogPanel( ) { | |||||
super(); | |||||
normalAttributeSet=new SimpleAttributeSet(); | |||||
normalAttributeSet.addAttribute(StyleConstants.Foreground,Color.black); | |||||
errorAttributeSet=new SimpleAttributeSet(); | |||||
errorAttributeSet.addAttribute(StyleConstants.Foreground,Color.red); | |||||
// initialize(); | |||||
} | |||||
/** | |||||
* Initialize the default state of the different GUI elements. | |||||
*/ | |||||
protected void initialize() { | |||||
GridBagLayout gbl_main=new GridBagLayout(); | |||||
this.setLayout(gbl_main); | |||||
logTextPane=new JTextPane(); | |||||
logStyleDocument=logTextPane.getStyledDocument(); | |||||
logScrollPane=new JScrollPane(logTextPane); | |||||
GridBagConstraints gbc_logScrollPane=new GridBagConstraints(); | |||||
gbc_logScrollPane.gridx=0; | |||||
gbc_logScrollPane.gridy=2; | |||||
gbc_logScrollPane.weightx=1.0d; | |||||
gbc_logScrollPane.weighty=1.0d; | |||||
gbc_logScrollPane.gridwidth=4; | |||||
gbc_logScrollPane.fill=GridBagConstraints.BOTH; | |||||
gbc_logScrollPane.insets=new Insets(5,0,0,0); | |||||
this.add(logScrollPane,gbc_logScrollPane); | |||||
clearLogButton=new JButton("clearLogButton"); | |||||
clearLogButton.addActionListener(new java.awt.event.ActionListener() { | |||||
@Override | |||||
public void actionPerformed(java.awt.event.ActionEvent e) { | |||||
logTextPane.setText(""); | |||||
} | |||||
}); | |||||
GridBagConstraints gbc_clearLogButton=new GridBagConstraints(); | |||||
gbc_clearLogButton.gridx=3; | |||||
gbc_clearLogButton.gridy=3; | |||||
gbc_clearLogButton.fill=GridBagConstraints.HORIZONTAL; | |||||
this.add(clearLogButton,gbc_clearLogButton); | |||||
} | |||||
/** | |||||
* Set the correct text for all graphical elements according to the default {@link Locale}. | |||||
* @see java.util.Locale#getDefault() | |||||
*/ | |||||
public void setText() { | |||||
ResourceBundle resourceBundle=ResourceBundle.getBundle("bundle/Bundle",Locale.getDefault()); | |||||
clearLogButton.setText(resourceBundle.getString("clear")); | |||||
} | |||||
/** | |||||
* Append data to the log. | |||||
* @param data message to append. | |||||
* @param error indicate if data correspond to an error or not. | |||||
*/ | |||||
public void addLog(String data,Boolean error) { | |||||
try { | |||||
if(logStyleDocument.getLength()>0) data="\n"+data; | |||||
if(error) { | |||||
logStyleDocument.insertString(logStyleDocument.getLength(), data, errorAttributeSet); | |||||
}else { | |||||
logStyleDocument.insertString(logStyleDocument.getLength(), data, normalAttributeSet); | |||||
} | |||||
}catch(BadLocationException e) { | |||||
logTextPane.setText(logTextPane.getText()+"\n"+data.trim().trim()); | |||||
} | |||||
} | |||||
} |
@ -0,0 +1,76 @@ | |||||
package main; | |||||
import java.util.Locale; | |||||
import java.util.ResourceBundle; | |||||
import generic.Status; | |||||
import gui.GUI; | |||||
import network.Client; | |||||
import network.CommunicationManager; | |||||
public class Primary { | |||||
public static void main(String[] args) { | |||||
String serverAddress="127.0.0.1"; | |||||
int serverPort=6200; | |||||
String command=""; | |||||
//Handle user arguments | |||||
if(args.length>0){ | |||||
for(int i=0;i<args.length;i++){ | |||||
if(args[i].startsWith("ip=")){ | |||||
serverAddress=args[i].substring(3); | |||||
} | |||||
if(args[i].startsWith("port=")){ | |||||
serverPort=Integer.valueOf(args[i].substring(5)); | |||||
} | |||||
if(args[i].startsWith("cmd=")){ | |||||
command=args[i].substring(4); | |||||
} | |||||
if(args[i].startsWith("help")||args[i].startsWith("?")){ | |||||
ResourceBundle resourceBundle=ResourceBundle.getBundle("bundle/Bundle",Locale.getDefault()); | |||||
System.out.println(resourceBundle.getString("help_intro")+"\n"); | |||||
System.out.println(resourceBundle.getString("help_argument")); | |||||
System.out.println("ip=X\t"+resourceBundle.getString("help_ip")); | |||||
System.out.println("port=X\t"+resourceBundle.getString("help_port")); | |||||
System.out.println("cmd=X\t"+resourceBundle.getString("help_cmd")); | |||||
System.out.println("help,?\t"+resourceBundle.getString("help_help")); | |||||
return; | |||||
} | |||||
} | |||||
} | |||||
Client client=new Client(serverPort,serverAddress); | |||||
CommunicationManager communicationManager=new CommunicationManager(client); | |||||
if(command.isBlank()) { | |||||
GUI gui=new GUI(communicationManager); | |||||
gui.setVisible(true); | |||||
return; | |||||
} | |||||
ResourceBundle resourceBundle=ResourceBundle.getBundle("bundle/Bundle",Locale.getDefault()); | |||||
Status<Object,Exception> connectStatus=communicationManager.connect(); | |||||
if(connectStatus.success==false) { | |||||
connectStatus.error.toString(); | |||||
return; | |||||
} | |||||
System.out.println(resourceBundle.getString("connectedTo")+": "+serverAddress+":"+serverPort); | |||||
Status<Object,Exception> sendStatus=communicationManager.encryptAndSendString(command); | |||||
if(sendStatus.success) { | |||||
Status<String,Exception> receiveStatus=communicationManager.receiveAndDecryptString(); | |||||
if(receiveStatus.success) { | |||||
System.out.println(receiveStatus.payload.trim()); | |||||
} else { | |||||
receiveStatus.error.toString(); | |||||
} | |||||
} else { | |||||
sendStatus.error.toString(); | |||||
} | |||||
communicationManager.disconnect(); | |||||
System.out.println(resourceBundle.getString("connectionEnded")); | |||||
} | |||||
} |
@ -0,0 +1,159 @@ | |||||
package network; | |||||
import java.io.BufferedReader; | |||||
import java.io.IOException; | |||||
import java.io.InputStreamReader; | |||||
import java.io.ObjectInputStream; | |||||
import java.io.ObjectOutputStream; | |||||
import java.io.PrintStream; | |||||
import java.net.Socket; | |||||
import generic.Status; | |||||
/** | |||||
* TCP client allowing to send and receive Objects. | |||||
*/ | |||||
public class Client { | |||||
private int serverPort; | |||||
private String serverAddress; | |||||
private Socket socket; | |||||
private BufferedReader is; | |||||
private PrintStream os; | |||||
private ObjectInputStream ois; | |||||
private ObjectOutputStream oos; | |||||
public Client(){ | |||||
serverPort=8033; | |||||
serverAddress="127.0.0.1"; | |||||
} | |||||
public Client(int port){ | |||||
serverPort=port; | |||||
serverAddress="127.0.0.1"; | |||||
} | |||||
public Client(int port,String ip){ | |||||
serverPort=port; | |||||
serverAddress=ip; | |||||
} | |||||
public int getServerPort() { | |||||
return serverPort; | |||||
} | |||||
public void setServerPort(int serverPort) { | |||||
this.serverPort = serverPort; | |||||
} | |||||
public String getServerAddress() { | |||||
return serverAddress; | |||||
} | |||||
public void setServerAddress(String serverAddress) { | |||||
this.serverAddress = serverAddress; | |||||
} | |||||
public Socket getSocket() { | |||||
return socket; | |||||
} | |||||
public void setSocket(Socket socket) { | |||||
this.socket = socket; | |||||
if(socket!=null) { | |||||
try { | |||||
setIs(new BufferedReader(new InputStreamReader(socket.getInputStream()))); | |||||
setOs(new PrintStream(socket.getOutputStream())); | |||||
setOos(new ObjectOutputStream(socket.getOutputStream())); | |||||
setOis(new ObjectInputStream(socket.getInputStream())); | |||||
} catch (Exception e) { | |||||
} | |||||
} | |||||
} | |||||
public BufferedReader getIs() { | |||||
return is; | |||||
} | |||||
public void setIs(BufferedReader is) { | |||||
this.is = is; | |||||
} | |||||
public PrintStream getOs() { | |||||
return os; | |||||
} | |||||
public void setOs(PrintStream os) { | |||||
this.os = os; | |||||
} | |||||
public ObjectInputStream getOis() { | |||||
return ois; | |||||
} | |||||
public void setOis(ObjectInputStream ois) { | |||||
this.ois = ois; | |||||
} | |||||
public ObjectOutputStream getOos() { | |||||
return oos; | |||||
} | |||||
public void setOos(ObjectOutputStream oos) { | |||||
this.oos = oos; | |||||
} | |||||
public Status<Object,Exception> connect(){ | |||||
Status<Object,Exception> result=new Status<Object,Exception>(true); | |||||
try { | |||||
socket=new Socket(serverAddress,serverPort); | |||||
is=new BufferedReader(new InputStreamReader(socket.getInputStream())); | |||||
os=new PrintStream(socket.getOutputStream()); | |||||
ois=new ObjectInputStream(socket.getInputStream()); | |||||
oos=new ObjectOutputStream(socket.getOutputStream()); | |||||
result.message=socket.getInetAddress().toString().substring(1)+":"+socket.getPort(); | |||||
} catch (Exception e) { | |||||
result.success=false; | |||||
result.error=e; | |||||
} | |||||
return result; | |||||
} | |||||
public Status<Object,Exception> sendString(String message){ | |||||
Status<Object,Exception> result=new Status<Object,Exception>(true); | |||||
try { | |||||
os.println(message); | |||||
} catch (Exception e) { | |||||
result.success=false; | |||||
result.error=e; | |||||
} | |||||
return result; | |||||
} | |||||
public Status<String,Exception> receiveString(){ | |||||
Status<String,Exception> result=new Status<String,Exception>(true); | |||||
try { | |||||
result.payload=is.readLine(); | |||||
} catch (IOException e) { | |||||
result.success=false; | |||||
result.error=e; | |||||
} | |||||
return result; | |||||
} | |||||
public Status<Object,IOException> sendObject(Object object){ | |||||
Status<Object,IOException> result=new Status<Object,IOException>(true); | |||||
try { | |||||
oos.writeObject(object); | |||||
} catch (IOException e) { | |||||
result.success=false; | |||||
result.error=e; | |||||
} | |||||
return result; | |||||
} | |||||
public Status<Object,Exception> receiveObject(){ | |||||
Status<Object,Exception> result=new Status<Object,Exception>(true); | |||||
try { | |||||
result.payload=ois.readObject(); | |||||
} catch (Exception e) { | |||||
result.success=false; | |||||
result.error=e; | |||||
} | |||||
return result; | |||||
} | |||||
public Status<Object,IOException> close(){ | |||||
Status<Object,IOException> result=new Status<Object,IOException>(true); | |||||
if(this.socket==null) return result; | |||||
try { | |||||
socket.close(); | |||||
} catch (IOException e) { | |||||
result.success=false; | |||||
result.error=e; | |||||
} | |||||
return result; | |||||
} | |||||
} |
@ -0,0 +1,340 @@ | |||||
package network; | |||||
import java.io.IOException; | |||||
import java.nio.charset.StandardCharsets; | |||||
import java.security.NoSuchAlgorithmException; | |||||
import java.util.Base64; | |||||
import javax.crypto.Cipher; | |||||
import javax.crypto.KeyGenerator; | |||||
import javax.crypto.SecretKey; | |||||
import javax.crypto.spec.SecretKeySpec; | |||||
import generic.Status; | |||||
public class CommunicationManager { | |||||
private Client client; | |||||
/** Key used to initialize the connection. */ | |||||
private SecretKey masterKey; | |||||
/** Communication key between the client and the server. */ | |||||
private SecretKey communicationKey; | |||||
/** If <code>true</code> then the message will be send using the communicationKey. Otherwise, the masterKey will be used.*/ | |||||
private boolean useCommunicationKey; | |||||
public CommunicationManager(Client client) { | |||||
this.client=client; | |||||
this.masterKey=new SecretKeySpec(new byte[]{-70,-45,-79,-32,-36,108,-117,-7,-97,56,46,-86,-35,-11,-35,-81}, "AES"); | |||||
this.useCommunicationKey=false; | |||||
} | |||||
public String getAddress() { | |||||
if(client!=null) return client.getServerAddress(); | |||||
return ""; | |||||
} | |||||
public int getPort() { | |||||
if(client!=null) return client.getServerPort(); | |||||
return 0; | |||||
} | |||||
/** | |||||
* Establish connection with the server set in the {@link #client}. | |||||
* If no error, then swap the key from master to communication. | |||||
* @return response {@link Status} object. | |||||
*/ | |||||
public Status<Object,Exception> connect() { | |||||
Status<Object,Exception> status=new Status<Object,Exception>(true); | |||||
//Stop immediately if already connected | |||||
if(client!=null && client.getSocket()!=null && client.getSocket().isConnected()) { | |||||
return status; | |||||
} | |||||
//Connect to server | |||||
status=client.connect(); | |||||
if(status.success==false) { | |||||
return status; | |||||
} | |||||
//Change from master key to a communication key | |||||
Status<String,Exception> keyStatus=client.receiveString(); | |||||
if(keyStatus.success==false) { | |||||
status.success=keyStatus.success; | |||||
status.error=keyStatus.error; | |||||
return status; | |||||
} | |||||
Status<String,Exception> decryptStatus=decryptString(keyStatus.payload, masterKey); | |||||
if(decryptStatus.success==false) { | |||||
status.success=decryptStatus.success; | |||||
status.error=decryptStatus.error; | |||||
return status; | |||||
} | |||||
String[] encodedKeyString=decryptStatus.payload.split(";"); | |||||
byte[] encodedKey=new byte[encodedKeyString.length]; | |||||
for(int i=0;i<encodedKeyString.length;i++) { | |||||
encodedKey[i]=Byte.parseByte(encodedKeyString[i]); | |||||
} | |||||
communicationKey=new SecretKeySpec(encodedKey, "AES"); | |||||
useCommunicationKey=true; | |||||
return status; | |||||
} | |||||
/** | |||||
* Establish connection with a server. | |||||
* @param address server address | |||||
* @param port server remote port | |||||
* @return response {@link Status} object. | |||||
*/ | |||||
public Status<Object,Exception> connect(String address,int port) { | |||||
if(client.getSocket()!=null && client.getSocket().isConnected()) { | |||||
disconnect(); | |||||
} | |||||
this.client=new Client(port, address); | |||||
return connect(); | |||||
} | |||||
/** | |||||
* Encrypt and send data. | |||||
* @param data data to send. | |||||
* @return response {@link Status} object. | |||||
*/ | |||||
public Status<Object,Exception> encryptAndSendString(String data) { | |||||
String encryptedMessage=""; | |||||
if(useCommunicationKey) { | |||||
encryptedMessage=encryptString(data, communicationKey).payload; | |||||
}else { | |||||
encryptedMessage=encryptString(data, masterKey).payload; | |||||
} | |||||
return client.sendString(encryptedMessage); | |||||
} | |||||
/** | |||||
* Receive and decrypt data. | |||||
* @return response {@link Status} object. | |||||
*/ | |||||
public Status<String,Exception> receiveAndDecryptString() { | |||||
Status<String,Exception> status=client.receiveString(); | |||||
if(status.success==false) { | |||||
status.success=status.success; | |||||
status.error=status.error; | |||||
return status; | |||||
} | |||||
if(useCommunicationKey) { | |||||
status=decryptString(status.payload, communicationKey); | |||||
}else { | |||||
status=decryptString(status.payload, masterKey); | |||||
} | |||||
if(status.success==false) { | |||||
status.success=status.success; | |||||
status.error=status.error; | |||||
} | |||||
return status; | |||||
} | |||||
/** | |||||
* Close the communication socket. | |||||
* Inform the remote client of the deconnection. | |||||
* @return response {@link Status} object. | |||||
*/ | |||||
public Status<Object,IOException> disconnect() { | |||||
encryptAndSendString("STOPstopSTOP"); | |||||
return client.close(); | |||||
} | |||||
/** | |||||
* Generate and setup a communication key to replace the master key for future communication. | |||||
* Send the new key to the client. | |||||
*/ | |||||
public void setupCommunicationKey() { | |||||
communicationKey=generateSecretKey(); | |||||
String encodedKey=""; | |||||
for(int i=0;i<communicationKey.getEncoded().length;i++) { | |||||
encodedKey+=communicationKey.getEncoded()[i]+";"; | |||||
} | |||||
encryptAndSendString(encodedKey); | |||||
useCommunicationKey=true; | |||||
} | |||||
/** | |||||
* Generates a secret key. | |||||
* @return secret key. | |||||
*/ | |||||
private SecretKey generateSecretKey() { | |||||
KeyGenerator keyGenerator = null; | |||||
try { | |||||
keyGenerator = KeyGenerator.getInstance("AES"); | |||||
} catch (NoSuchAlgorithmException e) { | |||||
//Never trigger as per the javax.crypto.KeyGenerator documentation: | |||||
//Every implementation of the Java platform is required to support | |||||
//the following standard KeyGenerator algorithms with the keysizes | |||||
//in parentheses: AES (128) | |||||
e.printStackTrace(); | |||||
} | |||||
keyGenerator.init(128); | |||||
return keyGenerator.generateKey(); | |||||
} | |||||
/** | |||||
* Encrypt a message. | |||||
* @param data message to encrypt. | |||||
* @return response {@link Status} object. | |||||
*/ | |||||
public Status<byte[],Exception> encrypt(byte[] data) { | |||||
if(useCommunicationKey) { | |||||
return encrypt(data,communicationKey); | |||||
}else{ | |||||
return encrypt(data,masterKey); | |||||
} | |||||
} | |||||
/** | |||||
* Encrypt a message with the specified key. | |||||
* @param data message to encrypt. | |||||
* @param secretKey secret key to use. | |||||
* @return response {@link Status} object. | |||||
*/ | |||||
private Status<byte[],Exception> encrypt(byte[] data,SecretKey secretKey) { | |||||
Status<byte[],Exception> result=new Status<byte[],Exception>(true); | |||||
try { | |||||
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding"); | |||||
cipher.init(Cipher.ENCRYPT_MODE, secretKey); | |||||
byte[] encrypt=cipher.doFinal(data); | |||||
/* | |||||
* Base64.getEncoder().encode() is here to | |||||
* prevent javax.crypto.IllegalBlockSizeException: | |||||
* Input length must be multiple of 16 when decrypting with padded cipher | |||||
*/ | |||||
result.payload=Base64.getEncoder().encode(encrypt); | |||||
} catch (Exception e) { | |||||
result.success=false; | |||||
result.error=e; | |||||
} | |||||
return result; | |||||
} | |||||
/** | |||||
* Encrypt a message. | |||||
* @param data message to encrypt. | |||||
* @return response {@link Status} object. | |||||
*/ | |||||
public Status<String,Exception> encryptString(String data) { | |||||
if(useCommunicationKey) { | |||||
return encryptString(data,communicationKey); | |||||
}else{ | |||||
return encryptString(data,masterKey); | |||||
} | |||||
} | |||||
/** | |||||
* Use {@link #encrypt(String, SecretKey)} to encrypt a message. | |||||
* @param data message to encrypt. | |||||
* @param secretKey secret key to use. | |||||
* @return response {@link Status} object. | |||||
*/ | |||||
private Status<String,Exception> encryptString(String data,SecretKey secretKey) { | |||||
Status<String,Exception> result=new Status<String,Exception>(true); | |||||
Status<byte[],Exception> encryptResult=encrypt(data.getBytes(StandardCharsets.UTF_8),secretKey); | |||||
result.message=encryptResult.message; | |||||
result.success=encryptResult.success; | |||||
result.error=encryptResult.error; | |||||
if(encryptResult.success) { | |||||
result.payload=new String(encryptResult.payload,StandardCharsets.UTF_8); | |||||
} | |||||
return result; | |||||
} | |||||
/*private String padStringForCrypto (String data) { | |||||
//Prevent javax.crypto.IllegalBlockSizeException: | |||||
//Input length must be multiple of 16 when decrypting with padded cipher | |||||
if(data.length()%16!=0) { | |||||
System.out.println("data length="+data.length()); | |||||
double dataLengthNeeded=Math.ceil((double)data.length()/16d)*16; | |||||
data=String.format("%-"+(int)dataLengthNeeded+"s", data); | |||||
System.out.println("data length="+data.length()); | |||||
} | |||||
return data; | |||||
}*/ | |||||
/** | |||||
* Decrypt a message. | |||||
* @param data encrypted message. | |||||
* @return response {@link Status} object. | |||||
*/ | |||||
public Status<byte[],Exception> decrypt(byte[] data){ | |||||
if(useCommunicationKey) { | |||||
return decrypt(data,communicationKey); | |||||
}else{ | |||||
return decrypt(data,masterKey); | |||||
} | |||||
} | |||||
/** | |||||
* Decrypt a message with the specified key. | |||||
* @param data encrypted message. | |||||
* @param secretKey secret key to use. | |||||
* @return response {@link Status} object. | |||||
*/ | |||||
private Status<byte[],Exception> decrypt(byte[] data,SecretKey secretKey) { | |||||
Status<byte[],Exception> result=new Status<byte[],Exception>(true); | |||||
try { | |||||
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding"); | |||||
cipher.init(Cipher.DECRYPT_MODE, secretKey); | |||||
/* | |||||
* Base64.getDecoder().decode() is here to | |||||
* prevent javax.crypto.IllegalBlockSizeException: | |||||
* Input length must be multiple of 16 when decrypting with padded cipher | |||||
*/ | |||||
result.payload=cipher.doFinal(Base64.getDecoder().decode(data)); | |||||
} catch (Exception e) { | |||||
result.success=false; | |||||
result.error=e; | |||||
} | |||||
return result; | |||||
} | |||||
/** | |||||
* Decrypt a {@link String}. | |||||
* @param data encrypted message. | |||||
* @return response {@link Status} object. | |||||
*/ | |||||
public Status<String,Exception> decryptString(String data) { | |||||
if(useCommunicationKey) { | |||||
return decryptString(data,communicationKey); | |||||
}else{ | |||||
return decryptString(data,masterKey); | |||||
} | |||||
} | |||||
/** | |||||
* Use {@link #encrypt(String, SecretKey)} to decrypt a message. | |||||
* @param data encrypted message. | |||||
* @param secretKey secret key to use. | |||||
* @return response {@link Status} object. | |||||
*/ | |||||
private Status<String,Exception> decryptString(String data,SecretKey secretKey) { | |||||
Status<String,Exception> result=new Status<String,Exception>(true); | |||||
byte[] dataByte; | |||||
try { | |||||
dataByte=data.getBytes(StandardCharsets.UTF_8); | |||||
} catch (NullPointerException e) { | |||||
result.error=e; | |||||
result.success=false; | |||||
return result; | |||||
} | |||||
Status<byte[],Exception> decryptResult=decrypt(dataByte,secretKey); | |||||
result.message=decryptResult.message; | |||||
result.success=decryptResult.success; | |||||
result.error=decryptResult.error; | |||||
if(decryptResult.success) { | |||||
result.payload=new String(decryptResult.payload,StandardCharsets.UTF_8); | |||||
} | |||||
return result; | |||||
} | |||||
} |
@ -0,0 +1,11 @@ | |||||
<?xml version="1.0" encoding="UTF-8"?> | |||||
<classpath> | |||||
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-15"> | |||||
<attributes> | |||||
<attribute name="module" value="true"/> | |||||
</attributes> | |||||
</classpathentry> | |||||
<classpathentry kind="src" path="src"/> | |||||
<classpathentry kind="src" path="resource"/> | |||||
<classpathentry kind="output" path="bin"/> | |||||
</classpath> |
@ -0,0 +1,91 @@ | |||||
######################## | |||||
#JAVA | |||||
######################## | |||||
# Compiled class file | |||||
*.class | |||||
# Log file | |||||
*.log | |||||
# BlueJ files | |||||
*.ctxt | |||||
# Mobile Tools for Java (J2ME) | |||||
.mtj.tmp/ | |||||
# Package Files # | |||||
*.jar | |||||
*.war | |||||
*.nar | |||||
*.ear | |||||
*.zip | |||||
*.tar.gz | |||||
*.rar | |||||
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml | |||||
hs_err_pid* | |||||
######################## | |||||
# ECLIPSE | |||||
######################## | |||||
.metadata | |||||
bin/ | |||||
tmp/ | |||||
*.tmp | |||||
*.bak | |||||
*.swp | |||||
*~.nib | |||||
local.properties | |||||
.settings/ | |||||
.loadpath | |||||
.recommenders | |||||
# External tool builders | |||||
.externalToolBuilders/ | |||||
# Locally stored "Eclipse launch configurations" | |||||
*.launch | |||||
# PyDev specific (Python IDE for Eclipse) | |||||
*.pydevproject | |||||
# CDT-specific (C/C++ Development Tooling) | |||||
.cproject | |||||
# CDT- autotools | |||||
.autotools | |||||
# Java annotation processor (APT) | |||||
.factorypath | |||||
# PDT-specific (PHP Development Tools) | |||||
.buildpath | |||||
# sbteclipse plugin | |||||
.target | |||||
# Tern plugin | |||||
.tern-project | |||||
# TeXlipse plugin | |||||
.texlipse | |||||
# STS (Spring Tool Suite) | |||||
.springBeans | |||||
# Code Recommenders | |||||
.recommenders/ | |||||
# Annotation Processing | |||||
.apt_generated/ | |||||
.apt_generated_test/ | |||||
# Scala IDE specific (Scala & Java development for Eclipse) | |||||
.cache-main | |||||
.scala_dependencies | |||||
.worksheet | |||||
# Uncomment this line if you wish to ignore the project description file. | |||||
# Typically, this file would be tracked if it contains build/dependency configurations: | |||||
#.project | |||||
@ -0,0 +1,17 @@ | |||||
<?xml version="1.0" encoding="UTF-8"?> | |||||
<projectDescription> | |||||
<name>server</name> | |||||
<comment></comment> | |||||
<projects> | |||||
</projects> | |||||
<buildSpec> | |||||
<buildCommand> | |||||
<name>org.eclipse.jdt.core.javabuilder</name> | |||||
<arguments> | |||||
</arguments> | |||||
</buildCommand> | |||||
</buildSpec> | |||||
<natures> | |||||
<nature>org.eclipse.jdt.core.javanature</nature> | |||||
</natures> | |||||
</projectDescription> |
@ -0,0 +1,34 @@ | |||||
clientConnection = Client connected | |||||
clientConnectionFail = Client connection failed | |||||
clientConnectionStop = Client disconnect | |||||
help_argument = Arguments: | |||||
help_help = Show this help message. | |||||
help_intro = CommandPC is a program for executing commands on a remote machine regardless of the operating system. | |||||
help_nogui = Don't start the graphical user interface. | |||||
help_port = Set the server port to X. | |||||
localPort = Local port | |||||
message = Message | |||||
messageFail = Error with the reception of the message | |||||
quit = Quit | |||||
serverEnd = Server stop | |||||
serverEndAction = Stop the server | |||||
serverStart = Server start | |||||
serverStartAction = Start the server | |||||
serverStartFail = Server start failed |
@ -0,0 +1,34 @@ | |||||
clientConnection = Client connect\u00E9 | |||||
clientConnectionFail = \u00C9chec de connexion du client | |||||
clientConnectionStop = D\u00E9connexion du client | |||||
help_argument = Arguments: | |||||
help_help = Affiche ce message d'aide. | |||||
help_intro = CommandPC est un programme destin\u00E9 \u00E0 ex\u00E9cuter des commandes sur un serveur distant, quelque soit son syst\u00E8me d'exploitation. | |||||
help_nogui = Ne d\u00E9mmare pas l'interface graphique. | |||||
help_port = D\u00E9fini le port du serveur. | |||||
localPort = Port local | |||||
message = Message | |||||
messageFail = Erreur lors de la r\u00E9ception du message | |||||
quit = Arr\u00EAter | |||||
serverEnd = Arr\u00EAt du serveur | |||||
serverEndAction = Arr\u00EAter le serveur | |||||
serverStart = D\u00E9marrage du serveur | |||||
serverStartAction = D\u00E9marrer le serveur | |||||
serverStartFail = \u00C9chec du d\u00E9marrage du serveur |
@ -0,0 +1,57 @@ | |||||
package command; | |||||
import java.io.BufferedReader; | |||||
import java.io.IOException; | |||||
import java.io.InputStreamReader; | |||||
import java.io.Serializable; | |||||
import generic.Status; | |||||
/** | |||||
* Executes specified string command by the environment in which the application is running. | |||||
*/ | |||||
public class ExecuteCommand implements Serializable{ | |||||
private static final long serialVersionUID = 1L; | |||||
private Runtime runtime; | |||||
private BufferedReader bufferReader; | |||||
public ExecuteCommand(){ | |||||
runtime=Runtime.getRuntime(); | |||||
} | |||||
public Runtime getRuntime() { | |||||
return runtime; | |||||
} | |||||
public void setRuntime(Runtime runtime) { | |||||
this.runtime = runtime; | |||||
} | |||||
/** | |||||
* Executes the specified string command. | |||||
* @param command a specified system command. | |||||
* @return response {@link Status} object. | |||||
* @see Runtime#exec(String) | |||||
*/ | |||||
public Status<String,IOException> executeCommand(String command){ | |||||
Status<String,IOException> result=new Status<String, IOException>(false); | |||||
String responseLine; | |||||
try { | |||||
Process execution=runtime.exec(command); | |||||
bufferReader=new BufferedReader(new InputStreamReader(execution.getInputStream())); | |||||
while ((responseLine = bufferReader.readLine()) != null){ | |||||
if(!(responseLine.isEmpty())){ | |||||
if(result.payload==null || result.payload.isBlank()) { | |||||
result.payload=responseLine; | |||||
}else { | |||||
result.payload=result.payload+"\n"+responseLine; | |||||
} | |||||
} | |||||
} | |||||
result.success=true; | |||||
} catch (IOException e) { | |||||
result.success=false; | |||||
result.error=e; | |||||
} | |||||
return result; | |||||
} | |||||
} |
@ -0,0 +1,53 @@ | |||||
package generic; | |||||
/** | |||||
* Generic response class. | |||||
*/ | |||||
public class Status<T,E> { | |||||
/** Indicate if the operation was a success. By default, <code>false</code>. */ | |||||
public boolean success; | |||||
/** Response message.*/ | |||||
public String message; | |||||
/** Response payload.*/ | |||||
public T payload; | |||||
/** Response error */ | |||||
public E error; | |||||
public Status() { | |||||
super(); | |||||
this.success=false; | |||||
} | |||||
public Status(boolean success) { | |||||
super(); | |||||
this.success = success; | |||||
} | |||||
public Status(boolean success, String message) { | |||||
super(); | |||||
this.success = success; | |||||
this.message = message; | |||||
} | |||||
public Status(boolean success, String message, T payload) { | |||||
super(); | |||||
this.success = success; | |||||
this.message = message; | |||||
this.payload = payload; | |||||
} | |||||
public Status(boolean success, String message, T payload, E error) { | |||||
super(); | |||||
this.success = success; | |||||
this.message = message; | |||||
this.payload = payload; | |||||
this.error = error; | |||||
} | |||||
@Override | |||||
public String toString() { | |||||
String result="Status [success="+success+", message="+message; | |||||
if(payload!=null) { | |||||
result+=", payload="+payload.toString(); | |||||
} | |||||
if(error!=null) { | |||||
result+=", error="+error.toString(); | |||||
} | |||||
return result+"]"; | |||||
} | |||||
} |
@ -0,0 +1,140 @@ | |||||
package gui; | |||||
import java.awt.AWTException; | |||||
import java.awt.Image; | |||||
import java.awt.MenuItem; | |||||
import java.awt.PopupMenu; | |||||
import java.awt.SystemTray; | |||||
import java.awt.Toolkit; | |||||
import java.awt.TrayIcon; | |||||
import java.awt.event.ActionEvent; | |||||
import java.net.InetAddress; | |||||
import java.util.Locale; | |||||
import java.util.ResourceBundle; | |||||
import thread.ServerThread; | |||||
public class GUI { | |||||
private TrayIcon trayIcon; | |||||
private Image iconServerStart; | |||||
private Image iconServerStop; | |||||
private PopupMenu trayPopupMenu; | |||||
private MenuItem trayPopupItemQuit; | |||||
private MenuItem trayPopupItemToggleServerState; | |||||
private boolean serverStarted; | |||||
private ServerThread serverThread; | |||||
public GUI(ServerThread serverThread) { | |||||
serverStarted=false; | |||||
this.serverThread=serverThread; | |||||
initialize(); | |||||
} | |||||
public void show() { | |||||
try { | |||||
SystemTray.getSystemTray().add(trayIcon); | |||||
} catch (AWTException e) { | |||||
e.printStackTrace(); | |||||
} | |||||
} | |||||
public void hide() { | |||||
SystemTray.getSystemTray().remove(trayIcon); | |||||
} | |||||
private void initialize() { | |||||
if(SystemTray.isSupported()==false) { | |||||
return; | |||||
} | |||||
//Setup TrayIcon image | |||||
iconServerStart=Toolkit.getDefaultToolkit().getImage(getClass().getResource("/iconStart.png")); | |||||
iconServerStop=Toolkit.getDefaultToolkit().getImage(getClass().getResource("/iconStop.png")); | |||||
//Setup popup menu | |||||
trayPopupItemQuit=new MenuItem("trayPopupItemQuit"); | |||||
trayPopupItemQuit.addActionListener(new java.awt.event.ActionListener() { | |||||
@Override | |||||
public void actionPerformed(ActionEvent e) { | |||||
serverThread.stopServerSocket(); | |||||
serverThread.stopClientsSocket(); | |||||
hide(); | |||||
} | |||||
}); | |||||
trayPopupItemToggleServerState=new MenuItem("trayPopupItemToggleServerState"); | |||||
trayPopupItemToggleServerState.addActionListener(new java.awt.event.ActionListener() { | |||||
@Override | |||||
public void actionPerformed(ActionEvent e) { | |||||
if(serverStarted) { | |||||
serverThread.stopServerSocket(); | |||||
serverThread.stopClientsSocket(); | |||||
} else { | |||||
if(serverThread.isAlive()==false) { | |||||
serverThread=new ServerThread(serverThread.getServer(), serverThread.getGui()); | |||||
serverThread.start(); | |||||
} | |||||
} | |||||
} | |||||
}); | |||||
trayPopupMenu=new PopupMenu(); | |||||
trayPopupMenu.add(trayPopupItemToggleServerState); | |||||
trayPopupMenu.addSeparator(); | |||||
trayPopupMenu.add(trayPopupItemQuit); | |||||
trayIcon=new TrayIcon(iconServerStop,"CommandPC",trayPopupMenu); | |||||
trayIcon.setImageAutoSize(true); | |||||
setText(); | |||||
} | |||||
/** | |||||
* Set the correct text for all graphical elements according to the default {@link Locale}. | |||||
* @see java.util.Locale#getDefault() | |||||
*/ | |||||
private void setText() { | |||||
ResourceBundle resourceBundle=ResourceBundle.getBundle("bundle/Bundle",Locale.getDefault()); | |||||
trayPopupItemQuit.setLabel(resourceBundle.getString("quit")); | |||||
setTextServerState(); | |||||
} | |||||
/** | |||||
* Set the correct text related to server state. | |||||
*/ | |||||
private void setTextServerState() { | |||||
ResourceBundle resourceBundle=ResourceBundle.getBundle("bundle/Bundle",Locale.getDefault()); | |||||
if(serverStarted) { | |||||
trayPopupItemToggleServerState.setLabel(resourceBundle.getString("serverEndAction")); | |||||
} else { | |||||
trayPopupItemToggleServerState.setLabel(resourceBundle.getString("serverStartAction")); | |||||
} | |||||
} | |||||
/** | |||||
* Indicate to the user that the server has started. | |||||
*/ | |||||
public void displayServerStart() { | |||||
serverStarted=true; | |||||
trayIcon.setImage(iconServerStart); | |||||
setTextServerState(); | |||||
} | |||||
/** | |||||
* Indicate to the user that the server has stop. | |||||
*/ | |||||
public void displayServerStop() { | |||||
serverStarted=false; | |||||
trayIcon.setImage(iconServerStop); | |||||
setTextServerState(); | |||||
} | |||||
/** | |||||
* Indicate to the user that a new client is connected. | |||||
* @param inetAddress client address | |||||
* @param port client port | |||||
* @param localPort local port | |||||
*/ | |||||
public void displayStartConnection(InetAddress inetAddress, int port, int localPort) { | |||||
// TODO displayStartConnection() | |||||
} | |||||
} |
@ -0,0 +1,48 @@ | |||||
package main; | |||||
import java.util.Locale; | |||||
import java.util.ResourceBundle; | |||||
import gui.GUI; | |||||
import network.Server; | |||||
import thread.ServerThread; | |||||
public class Primary { | |||||
public static void main(String[] args) { | |||||
int port=6200; | |||||
boolean showGUI=true; | |||||
//Handle user arguments | |||||
if(args.length>0){ | |||||
for(int i=0;i<args.length;i++){ | |||||
if(args[i].startsWith("port=")){ | |||||
port=Integer.valueOf(args[i].substring(3)); | |||||
} | |||||
if(args[i].startsWith("nogui")){ | |||||
showGUI=false; | |||||
} | |||||
if(args[i].startsWith("help")||args[i].startsWith("?")){ | |||||
ResourceBundle resourceBundle=ResourceBundle.getBundle("bundle/Bundle",Locale.getDefault()); | |||||
System.out.println(resourceBundle.getString("help_intro")+"\n"); | |||||
System.out.println(resourceBundle.getString("help_argument")); | |||||
System.out.println("port=X\t"+resourceBundle.getString("help_port")); | |||||
System.out.println("nogui\t"+resourceBundle.getString("help_nogui")); | |||||
System.out.println("help,?\t"+resourceBundle.getString("help_help")); | |||||
return; | |||||
} | |||||
} | |||||
} | |||||
Server server=new Server(port); | |||||
ServerThread serverThread=new ServerThread(server); | |||||
if(showGUI) { | |||||
GUI gui=new GUI(serverThread); | |||||
gui.show(); | |||||
serverThread.setGui(gui); | |||||
} | |||||
serverThread.start(); | |||||
} | |||||
} |
@ -0,0 +1,159 @@ | |||||
package network; | |||||
import java.io.BufferedReader; | |||||
import java.io.IOException; | |||||
import java.io.InputStreamReader; | |||||
import java.io.ObjectInputStream; | |||||
import java.io.ObjectOutputStream; | |||||
import java.io.PrintStream; | |||||
import java.net.Socket; | |||||
import generic.Status; | |||||
/** | |||||
* TCP client allowing to send and receive Objects. | |||||
*/ | |||||
public class Client { | |||||
private int serverPort; | |||||
private String serverAddress; | |||||
private Socket socket; | |||||
private BufferedReader is; | |||||
private PrintStream os; | |||||
private ObjectInputStream ois; | |||||
private ObjectOutputStream oos; | |||||
public Client(){ | |||||
serverPort=8033; | |||||
serverAddress="127.0.0.1"; | |||||
} | |||||
public Client(int port){ | |||||
serverPort=port; | |||||
serverAddress="127.0.0.1"; | |||||
} | |||||
public Client(int port,String ip){ | |||||
serverPort=port; | |||||
serverAddress=ip; | |||||
} | |||||
public int getServerPort() { | |||||
return serverPort; | |||||
} | |||||
public void setServerPort(int serverPort) { | |||||
this.serverPort = serverPort; | |||||
} | |||||
public String getServerAddress() { | |||||
return serverAddress; | |||||
} | |||||
public void setServerAddress(String serverAddress) { | |||||
this.serverAddress = serverAddress; | |||||
} | |||||
public Socket getSocket() { | |||||
return socket; | |||||
} | |||||
public void setSocket(Socket socket) { | |||||
this.socket = socket; | |||||
if(socket!=null) { | |||||
try { | |||||
setIs(new BufferedReader(new InputStreamReader(socket.getInputStream()))); | |||||
setOs(new PrintStream(socket.getOutputStream())); | |||||
setOos(new ObjectOutputStream(socket.getOutputStream())); | |||||
setOis(new ObjectInputStream(socket.getInputStream())); | |||||
} catch (Exception e) { | |||||
} | |||||
} | |||||
} | |||||
public BufferedReader getIs() { | |||||
return is; | |||||
} | |||||
public void setIs(BufferedReader is) { | |||||
this.is = is; | |||||
} | |||||
public PrintStream getOs() { | |||||
return os; | |||||
} | |||||
public void setOs(PrintStream os) { | |||||
this.os = os; | |||||
} | |||||
public ObjectInputStream getOis() { | |||||
return ois; | |||||
} | |||||
public void setOis(ObjectInputStream ois) { | |||||
this.ois = ois; | |||||
} | |||||
public ObjectOutputStream getOos() { | |||||
return oos; | |||||
} | |||||
public void setOos(ObjectOutputStream oos) { | |||||
this.oos = oos; | |||||
} | |||||
public Status<Object,Exception> connect(){ | |||||
Status<Object,Exception> result=new Status<Object,Exception>(true); | |||||
try { | |||||
socket=new Socket(serverAddress,serverPort); | |||||
is=new BufferedReader(new InputStreamReader(socket.getInputStream())); | |||||
os=new PrintStream(socket.getOutputStream()); | |||||
ois=new ObjectInputStream(socket.getInputStream()); | |||||
oos=new ObjectOutputStream(socket.getOutputStream()); | |||||
result.message=socket.getInetAddress().toString().substring(1)+":"+socket.getPort(); | |||||
} catch (Exception e) { | |||||
result.success=false; | |||||
result.error=e; | |||||
} | |||||
return result; | |||||
} | |||||
public Status<Object,Exception> sendString(String message){ | |||||
Status<Object,Exception> result=new Status<Object,Exception>(true); | |||||
try { | |||||
os.println(message); | |||||
} catch (Exception e) { | |||||
result.success=false; | |||||
result.error=e; | |||||
} | |||||
return result; | |||||
} | |||||
public Status<String,Exception> receiveString(){ | |||||
Status<String,Exception> result=new Status<String,Exception>(true); | |||||
try { | |||||
result.payload=is.readLine(); | |||||
} catch (IOException e) { | |||||
result.success=false; | |||||
result.error=e; | |||||
} | |||||
return result; | |||||
} | |||||
public Status<Object,IOException> sendObject(Object object){ | |||||
Status<Object,IOException> result=new Status<Object,IOException>(true); | |||||
try { | |||||
oos.writeObject(object); | |||||
} catch (IOException e) { | |||||
result.success=false; | |||||
result.error=e; | |||||
} | |||||
return result; | |||||
} | |||||
public Status<Object,Exception> receiveObject(){ | |||||
Status<Object,Exception> result=new Status<Object,Exception>(true); | |||||
try { | |||||
result.payload=ois.readObject(); | |||||
} catch (Exception e) { | |||||
result.success=false; | |||||
result.error=e; | |||||
} | |||||
return result; | |||||
} | |||||
public Status<Object,IOException> close(){ | |||||
Status<Object,IOException> result=new Status<Object,IOException>(true); | |||||
if(this.socket==null) return result; | |||||
try { | |||||
socket.close(); | |||||
} catch (IOException e) { | |||||
result.success=false; | |||||
result.error=e; | |||||
} | |||||
return result; | |||||
} | |||||
} |
@ -0,0 +1,333 @@ | |||||
package network; | |||||
import java.io.IOException; | |||||
import java.nio.charset.StandardCharsets; | |||||
import java.security.NoSuchAlgorithmException; | |||||
import java.util.Base64; | |||||
import javax.crypto.Cipher; | |||||
import javax.crypto.KeyGenerator; | |||||
import javax.crypto.SecretKey; | |||||
import javax.crypto.spec.SecretKeySpec; | |||||
import generic.Status; | |||||
public class CommunicationManager { | |||||
private Client client; | |||||
/** Key used to initialize the connection. */ | |||||
private SecretKey masterKey; | |||||
/** Communication key between the client and the server. */ | |||||
private SecretKey communicationKey; | |||||
/** If <code>true</code> then the message will be send using the communicationKey. Otherwise, the masterKey will be used.*/ | |||||
private boolean useCommunicationKey; | |||||
public CommunicationManager(Client client) { | |||||
this.client=client; | |||||
this.masterKey=new SecretKeySpec(new byte[]{-70,-45,-79,-32,-36,108,-117,-7,-97,56,46,-86,-35,-11,-35,-81}, "AES"); | |||||
this.useCommunicationKey=false; | |||||
} | |||||
public String getAddress() { | |||||
if(client!=null) return client.getServerAddress(); | |||||
return ""; | |||||
} | |||||
public int getPort() { | |||||
if(client!=null) return client.getServerPort(); | |||||
return 0; | |||||
} | |||||
/** | |||||
* Establish connection with the server set in the {@link #client}. | |||||
* If no error, then swap the key from master to communication. | |||||
* @return response {@link Status} object. | |||||
*/ | |||||
public Status<Object,Exception> connect() { | |||||
Status<Object,Exception> status=new Status<Object,Exception>(true); | |||||
//Stop immediately if already connected | |||||
if(client!=null && client.getSocket()!=null && client.getSocket().isConnected()) { | |||||
return status; | |||||
} | |||||
//Connect to server | |||||
status=client.connect(); | |||||
if(status.success==false) { | |||||
return status; | |||||
} | |||||
//Change from master key to a communication key | |||||
Status<String,Exception> keyStatus=client.receiveString(); | |||||
if(keyStatus.success==false) { | |||||
status.success=keyStatus.success; | |||||
status.error=keyStatus.error; | |||||
return status; | |||||
} | |||||
Status<String,Exception> decryptStatus=decryptString(keyStatus.payload, masterKey); | |||||
if(decryptStatus.success==false) { | |||||
status.success=decryptStatus.success; | |||||
status.error=decryptStatus.error; | |||||
return status; | |||||
} | |||||
String[] encodedKeyString=decryptStatus.payload.split(";"); | |||||
byte[] encodedKey=new byte[encodedKeyString.length]; | |||||
for(int i=0;i<encodedKeyString.length;i++) { | |||||
encodedKey[i]=Byte.parseByte(encodedKeyString[i]); | |||||
} | |||||
communicationKey=new SecretKeySpec(encodedKey, "AES"); | |||||
useCommunicationKey=true; | |||||
return status; | |||||
} | |||||
/** | |||||
* Establish connection with a server. | |||||
* @param address server address | |||||
* @param port server remote port | |||||
* @return response {@link Status} object. | |||||
*/ | |||||
public Status<Object,Exception> connect(String address,int port) { | |||||
if(client.getSocket()!=null && client.getSocket().isConnected()) { | |||||
disconnect(); | |||||
} | |||||
this.client=new Client(port, address); | |||||
return connect(); | |||||
} | |||||
/** | |||||
* Encrypt and send data. | |||||
* @param data data to send. | |||||
* @return response {@link Status} object. | |||||
*/ | |||||
public Status<Object,Exception> encryptAndSendString(String data) { | |||||
String encryptedMessage=""; | |||||
if(useCommunicationKey) { | |||||
encryptedMessage=encryptString(data, communicationKey).payload; | |||||
}else { | |||||
encryptedMessage=encryptString(data, masterKey).payload; | |||||
} | |||||
return client.sendString(encryptedMessage); | |||||
} | |||||
/** | |||||
* Receive and decrypt data. | |||||
* @return response {@link Status} object. | |||||
*/ | |||||
public Status<String,Exception> receiveAndDecryptString() { | |||||
Status<String,Exception> status=client.receiveString(); | |||||
if(status.success==false) { | |||||
status.success=status.success; | |||||
status.error=status.error; | |||||
return status; | |||||
} | |||||
if(useCommunicationKey) { | |||||
status=decryptString(status.payload, communicationKey); | |||||
}else { | |||||
status=decryptString(status.payload, masterKey); | |||||
} | |||||
if(status.success==false) { | |||||
status.success=status.success; | |||||
status.error=status.error; | |||||
} | |||||
return status; | |||||
} | |||||
/** | |||||
* Close the communication socket. | |||||
* Inform the remote client of the deconnection. | |||||
* @return response {@link Status} object. | |||||
*/ | |||||
public Status<Object,IOException> disconnect() { | |||||
encryptAndSendString("STOPstopSTOP"); | |||||
return client.close(); | |||||
} | |||||
/** | |||||
* Generate and setup a communication key to replace the master key for future communication. | |||||
* Send the new key to the client. | |||||
*/ | |||||
public void setupCommunicationKey() { | |||||
communicationKey=generateSecretKey(); | |||||
String encodedKey=""; | |||||
for(int i=0;i<communicationKey.getEncoded().length;i++) { | |||||
encodedKey+=communicationKey.getEncoded()[i]+";"; | |||||
} | |||||
encryptAndSendString(encodedKey); | |||||
useCommunicationKey=true; | |||||
} | |||||
/** | |||||
* Generates a secret key. | |||||
* @return secret key. | |||||
*/ | |||||
private SecretKey generateSecretKey() { | |||||
KeyGenerator keyGenerator = null; | |||||
try { | |||||
keyGenerator = KeyGenerator.getInstance("AES"); | |||||
} catch (NoSuchAlgorithmException e) { | |||||
//Never trigger as per the javax.crypto.KeyGenerator documentation: | |||||
//Every implementation of the Java platform is required to support | |||||
//the following standard KeyGenerator algorithms with the keysizes | |||||
//in parentheses: AES (128) | |||||
e.printStackTrace(); | |||||
} | |||||
keyGenerator.init(128); | |||||
return keyGenerator.generateKey(); | |||||
} | |||||
/** | |||||
* Encrypt a message. | |||||
* @param data message to encrypt. | |||||
* @return response {@link Status} object. | |||||
*/ | |||||
public Status<byte[],Exception> encrypt(byte[] data) { | |||||
if(useCommunicationKey) { | |||||
return encrypt(data,communicationKey); | |||||
}else{ | |||||
return encrypt(data,masterKey); | |||||
} | |||||
} | |||||
/** | |||||
* Encrypt a message with the specified key. | |||||
* @param data message to encrypt. | |||||
* @param secretKey secret key to use. | |||||
* @return response {@link Status} object. | |||||
*/ | |||||
private Status<byte[],Exception> encrypt(byte[] data,SecretKey secretKey) { | |||||
Status<byte[],Exception> result=new Status<byte[],Exception>(true); | |||||
try { | |||||
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding"); | |||||
cipher.init(Cipher.ENCRYPT_MODE, secretKey); | |||||
byte[] encrypt=cipher.doFinal(data); | |||||
/* | |||||
* Base64.getEncoder().encode() is here to | |||||
* prevent javax.crypto.IllegalBlockSizeException: | |||||
* Input length must be multiple of 16 when decrypting with padded cipher | |||||
*/ | |||||
result.payload=Base64.getEncoder().encode(encrypt); | |||||
} catch (Exception e) { | |||||
result.success=false; | |||||
result.error=e; | |||||
} | |||||
return result; | |||||
} | |||||
/** | |||||
* Encrypt a message. | |||||
* @param data message to encrypt. | |||||
* @return response {@link Status} object. | |||||
*/ | |||||
public Status<String,Exception> encryptString(String data) { | |||||
if(useCommunicationKey) { | |||||
return encryptString(data,communicationKey); | |||||
}else{ | |||||
return encryptString(data,masterKey); | |||||
} | |||||
} | |||||
/** | |||||
* Use {@link #encrypt(String, SecretKey)} to encrypt a message. | |||||
* @param data message to encrypt. | |||||
* @param secretKey secret key to use. | |||||
* @return response {@link Status} object. | |||||
*/ | |||||
private Status<String,Exception> encryptString(String data,SecretKey secretKey) { | |||||
Status<String,Exception> result=new Status<String,Exception>(true); | |||||
Status<byte[],Exception> encryptResult=encrypt(data.getBytes(StandardCharsets.UTF_8),secretKey); | |||||
result.message=encryptResult.message; | |||||
result.success=encryptResult.success; | |||||
result.error=encryptResult.error; | |||||
if(encryptResult.success) { | |||||
result.payload=new String(encryptResult.payload,StandardCharsets.UTF_8); | |||||
} | |||||
return result; | |||||
} | |||||
/*private String padStringForCrypto (String data) { | |||||
//Prevent javax.crypto.IllegalBlockSizeException: | |||||
//Input length must be multiple of 16 when decrypting with padded cipher | |||||
if(data.length()%16!=0) { | |||||
System.out.println("data length="+data.length()); | |||||
double dataLengthNeeded=Math.ceil((double)data.length()/16d)*16; | |||||
data=String.format("%-"+(int)dataLengthNeeded+"s", data); | |||||
System.out.println("data length="+data.length()); | |||||
} | |||||
return data; | |||||
}*/ | |||||
/** | |||||
* Decrypt a message. | |||||
* @param data encrypted message. | |||||
* @return response {@link Status} object. | |||||
*/ | |||||
public Status<byte[],Exception> decrypt(byte[] data){ | |||||
if(useCommunicationKey) { | |||||
return decrypt(data,communicationKey); | |||||
}else{ | |||||
return decrypt(data,masterKey); | |||||
} | |||||
} | |||||
/** | |||||
* Decrypt a message with the specified key. | |||||
* @param data encrypted message. | |||||
* @param secretKey secret key to use. | |||||
* @return response {@link Status} object. | |||||
*/ | |||||
private Status<byte[],Exception> decrypt(byte[] data,SecretKey secretKey) { | |||||
Status<byte[],Exception> result=new Status<byte[],Exception>(true); | |||||
try { | |||||
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding"); | |||||
cipher.init(Cipher.DECRYPT_MODE, secretKey); | |||||
/* | |||||
* Base64.getDecoder().decode() is here to | |||||
* prevent javax.crypto.IllegalBlockSizeException: | |||||
* Input length must be multiple of 16 when decrypting with padded cipher | |||||
*/ | |||||
result.payload=cipher.doFinal(Base64.getDecoder().decode(data)); | |||||
} catch (Exception e) { | |||||
result.success=false; | |||||
result.error=e; | |||||
} | |||||
return result; | |||||
} | |||||
/** | |||||
* Decrypt a {@link String}. | |||||
* @param data encrypted message. | |||||
* @return response {@link Status} object. | |||||
*/ | |||||
public Status<String,Exception> decryptString(String data) { | |||||
if(useCommunicationKey) { | |||||
return decryptString(data,communicationKey); | |||||
}else{ | |||||
return decryptString(data,masterKey); | |||||
} | |||||
} | |||||
/** | |||||
* Use {@link #encrypt(String, SecretKey)} to decrypt a message. | |||||
* @param data encrypted message. | |||||
* @param secretKey secret key to use. | |||||
* @return response {@link Status} object. | |||||
*/ | |||||
private Status<String,Exception> decryptString(String data,SecretKey secretKey) { | |||||
Status<String,Exception> result=new Status<String,Exception>(true); | |||||
byte[] dataByte=data.getBytes(StandardCharsets.UTF_8); | |||||
Status<byte[],Exception> decryptResult=decrypt(dataByte,secretKey); | |||||
result.message=decryptResult.message; | |||||
result.success=decryptResult.success; | |||||
result.error=decryptResult.error; | |||||
if(decryptResult.success) { | |||||
result.payload=new String(decryptResult.payload,StandardCharsets.UTF_8); | |||||
} | |||||
return result; | |||||
} | |||||
} |
@ -0,0 +1,57 @@ | |||||
package network; | |||||
import java.io.IOException; | |||||
import java.net.ServerSocket; | |||||
import java.net.Socket; | |||||
import java.util.ArrayList; | |||||
import java.util.List; | |||||
import generic.Status; | |||||
/** | |||||
* TCP server allowing to send and receive Objects. | |||||
*/ | |||||
public class Server { | |||||
private int serverPort; | |||||
private ServerSocket serverSocket; | |||||
private List<Socket> clientSocket; | |||||
public Server(int serverPort) { | |||||
super(); | |||||
this.serverPort = serverPort; | |||||
clientSocket=new ArrayList<Socket>(); | |||||
} | |||||
public Status<Integer,IOException> start() { | |||||
Status<Integer,IOException> result=new Status<Integer,IOException>(true); | |||||
try { | |||||
serverSocket=new ServerSocket(serverPort); | |||||
result.payload=serverSocket.getLocalPort(); | |||||
} catch (IOException e) { | |||||
result.success=false; | |||||
result.error=e; | |||||
}; | |||||
return result; | |||||
} | |||||
public Status<Socket,IOException> accept() { | |||||
Status<Socket,IOException> result=new Status<Socket,IOException>(true); | |||||
try { | |||||
Socket client=serverSocket.accept(); | |||||
clientSocket.add(client); | |||||
result.payload=client; | |||||
} catch (IOException e) { | |||||
result.success=false; | |||||
result.error=e; | |||||
}; | |||||
return result; | |||||
} | |||||
public void stop() { | |||||
try { | |||||
serverSocket.close(); | |||||
} catch (IOException e) { | |||||
// TODO Auto-generated catch block | |||||
e.printStackTrace(); | |||||
} | |||||
} | |||||
} |
@ -0,0 +1,98 @@ | |||||
package thread; | |||||
import java.io.IOException; | |||||
import java.io.UnsupportedEncodingException; | |||||
import java.net.Socket; | |||||
import java.util.Locale; | |||||
import java.util.ResourceBundle; | |||||
import command.ExecuteCommand; | |||||
import generic.Status; | |||||
import network.Client; | |||||
import network.CommunicationManager; | |||||
/** | |||||
* Thread dedicated to handling a communication with a client. | |||||
*/ | |||||
public class ClientThread extends Thread{ | |||||
private CommunicationManager communicationManager; | |||||
private Client client; | |||||
private ExecuteCommand executeCommand; | |||||
private boolean run; | |||||
private boolean osIsWindows; | |||||
public boolean isRun() { | |||||
return run; | |||||
} | |||||
public void setRun(boolean run) { | |||||
this.run = run; | |||||
} | |||||
public ClientThread(Socket socket) { | |||||
super(); | |||||
this.client=new Client(); | |||||
this.client.setSocket(socket); | |||||
this.communicationManager=new CommunicationManager(client); | |||||
this.executeCommand=new ExecuteCommand(); | |||||
this.run=true; | |||||
this.osIsWindows=System.getProperty("os.name").toLowerCase().startsWith("windows"); | |||||
} | |||||
private void closeConnection() { | |||||
communicationManager.encryptAndSendString("STOPstopSTOP"); | |||||
client.close(); | |||||
run=false; | |||||
} | |||||
@Override | |||||
public void run() { | |||||
communicationManager.setupCommunicationKey(); | |||||
Status<String,Exception> status; | |||||
while(run) { | |||||
status=client.receiveString(); | |||||
if(status.success && status.payload==null && status.error==null) { | |||||
//Client is probably disconnected and client.receiveString() is just "purging" buffer | |||||
closeConnection(); | |||||
} | |||||
if(run && status.success) { | |||||
status=communicationManager.decryptString(status.payload); | |||||
} | |||||
if(run) { | |||||
if(status.success) { | |||||
if(status.payload.compareTo("STOPstopSTOP")==0) { | |||||
closeConnection(); | |||||
}else { | |||||
System.out.println( | |||||
ResourceBundle.getBundle("bundle/Bundle",Locale.getDefault()).getString("message") | |||||
+": "+status.payload | |||||
); | |||||
Status<String, IOException> resultCommand=executeCommand.executeCommand(status.payload); | |||||
if(resultCommand.success) { | |||||
if(osIsWindows) { | |||||
try { | |||||
resultCommand.payload=new String(resultCommand.payload.getBytes(),"Cp850"); | |||||
} catch (UnsupportedEncodingException e) { | |||||
//If encoding fail do nothing as the text will still be mostly legible | |||||
} | |||||
} | |||||
communicationManager.encryptAndSendString(resultCommand.payload); | |||||
}else{ | |||||
communicationManager.encryptAndSendString(resultCommand.error.toString()); | |||||
} | |||||
} | |||||
}else { | |||||
System.out.println( | |||||
ResourceBundle.getBundle("bundle/Bundle",Locale.getDefault()).getString("messageFail") | |||||
+": "+status.error.toString() | |||||
); | |||||
status.error.printStackTrace(); | |||||
} | |||||
} | |||||
} | |||||
System.out.println(ResourceBundle.getBundle("bundle/Bundle",Locale.getDefault()).getString("clientConnectionStop")); | |||||
} | |||||
} |
@ -0,0 +1,114 @@ | |||||
package thread; | |||||
import java.io.IOException; | |||||
import java.net.Socket; | |||||
import java.util.ArrayList; | |||||
import java.util.List; | |||||
import java.util.Locale; | |||||
import java.util.ResourceBundle; | |||||
import generic.Status; | |||||
import gui.GUI; | |||||
import network.Server; | |||||
/** | |||||
* Thread dedicated to create {@link ClientThread} upon client connection. | |||||
*/ | |||||
public class ServerThread extends Thread{ | |||||
private Server server; | |||||
private Boolean accept; | |||||
private GUI gui; | |||||
private List<ClientThread> clients; | |||||
public Server getServer() { | |||||
return server; | |||||
} | |||||
public void setServer(Server server) { | |||||
this.server = server; | |||||
} | |||||
public GUI getGui() { | |||||
return gui; | |||||
} | |||||
public void setGui(GUI gui) { | |||||
this.gui = gui; | |||||
} | |||||
public Boolean getAccept() { | |||||
return accept; | |||||
} | |||||
public void setAccept(Boolean accept) { | |||||
this.accept = accept; | |||||
} | |||||
public ServerThread(Server server){ | |||||
super(); | |||||
this.clients=new ArrayList<ClientThread>(); | |||||
this.server=server; | |||||
this.accept=true; | |||||
this.gui=null; | |||||
} | |||||
public ServerThread(Server server,GUI gui){ | |||||
super(); | |||||
this.server=server; | |||||
this.accept=true; | |||||
this.gui=gui; | |||||
} | |||||
@Override | |||||
public void run(){ | |||||
accept=true; | |||||
Status<Integer,IOException> serverStartStatus=server.start(); | |||||
if(serverStartStatus.success) { | |||||
System.out.println(ResourceBundle.getBundle("bundle/Bundle",Locale.getDefault()).getString("serverStart")); | |||||
if(gui!=null) gui.displayServerStart(); | |||||
while(accept) { | |||||
Status<Socket,IOException> connectionAttempt=server.accept(); | |||||
if(connectionAttempt.success) { | |||||
Socket client=connectionAttempt.payload; | |||||
System.out.println( | |||||
ResourceBundle.getBundle("bundle/Bundle",Locale.getDefault()).getString("clientConnection") | |||||
+" "+client.getInetAddress()+":"+client.getPort() | |||||
+" (" | |||||
+ResourceBundle.getBundle("bundle/Bundle",Locale.getDefault()).getString("localPort") | |||||
+": "+client.getLocalPort() | |||||
+")" | |||||
); | |||||
if(gui!=null) gui.displayStartConnection(client.getInetAddress(),client.getPort(),client.getLocalPort()); | |||||
ClientThread ct=new ClientThread(client); | |||||
clients.add(ct); | |||||
ct.start(); | |||||
}else { | |||||
if(accept) { | |||||
System.out.println(ResourceBundle.getBundle("bundle/Bundle",Locale.getDefault()).getString("clientConnectionFail")); | |||||
} | |||||
} | |||||
} | |||||
}else { | |||||
System.out.println(ResourceBundle.getBundle("bundle/Bundle",Locale.getDefault()).getString("serverStartFail")); | |||||
System.out.println(serverStartStatus.error.toString()); | |||||
return; | |||||
} | |||||
if(gui!=null) gui.displayServerStop(); | |||||
System.out.println(ResourceBundle.getBundle("bundle/Bundle",Locale.getDefault()).getString("serverEnd")); | |||||
} | |||||
/** | |||||
* Stop the current server from accepting new connection. | |||||
* Old connection will remain active. | |||||
* @see #stopClientsSocket() | |||||
*/ | |||||
public void stopServerSocket() { | |||||
server.stop(); | |||||
accept=false; | |||||
} | |||||
/** | |||||
* Stop all the client connection. | |||||
* New connection are still possible. | |||||
* @see #stopServerSocket() | |||||
*/ | |||||
public void stopClientsSocket() { | |||||
for(int i=0;i<clients.size();i++) { | |||||
clients.get(i).setRun(false); | |||||
} | |||||
} | |||||
} |