Commit 1c7fb75e authored by Paul Fiterau Brostean's avatar Paul Fiterau Brostean

Some more updates on the model checking part, updated references...

parent b6ed7566
......@@ -2,11 +2,11 @@
The SSH protocol is used interact securely with remote machines. Alongside TLS and IPSec, SSH is amongst the most frequently used security suites~\cite{Albrecht2009Plaintext}. Due to its significant user base and sensitive nature, flaws in the protocol or its implementation could have major impact. It therefore comes as no surprise that SSH has attracted scrutiny from the security community. Problems with information leakage via unencrypted MACs were identified~\cite{Bellare2004Breaking}, as well as possibilities for plain text recovery when a block cipher in cipher block chaining mode was used~\cite{Albrecht2009Plaintext}. Other analysis found no serious issues~\cite{Williams2011Analysis}\cite{Paterson2010PlaintextDependent}. Academic researchers have so far focused more on the theoretical aspects than on implementations of the protocol.
In this work, we use classical active automata learning, which we refer to as model learning, to infer state machines of 2 SSH implementations. Model learning has previously been applied to infer state machines of EMV bank cards~\cite{Aarts2013Formal}, electronic passports~\cite{Aarts2010Inference} and hand-held readers for online banking~\cite{Chalupar2014Automated}. More recently, it was used to learn implementations of TCP~\cite{Janssen2015Learning} and TLS~\cite{RuiterProtocol}. Model learning's goal is obtaining a state model of a black-box system by providing inputs and observing outputs. The learned state model corresponds to the observed behavior, and can be used in system analysis. Since model learning builds from a finite number of observations, we can never be sure that the learned model is correct. To that end, advanced conformance algorithms are employed, which yield some confidence that the system inferred is in fact correct. In the context of testing protocols, model learning can be seen as a form of protocol state fuzzing, whereby unexpected inputs are sent to a system under test in the hope of exposing hidden anomalies. In model learning, inputs are sent with no regard to the order imposed by the protocol. Inputs can therefore be sent disorderly. Any anomalies are then exposed in the learned model.
In this work, we use classical active automata learning, which we refer to as model learning, to infer state machines of 2 SSH implementations. Model learning has previously been applied to infer state machines of EMV bank cards~\cite{Aarts2013Formal}, electronic passports~\cite{Aarts2010Inference} and hand-held readers for online banking~\cite{Chalupar2014Automated}. More recently, it was used to learn implementations of TCP~\cite{TCP2016} and TLS~\cite{RuiterProtocol}. Model learning's goal is obtaining a state model of a black-box system by providing inputs and observing outputs. The learned state model corresponds to the observed behavior, and can be used in system analysis. Since model learning builds from a finite number of observations, we can never be sure that the learned model is correct. To that end, advanced conformance algorithms are employed, which yield some confidence that the system inferred is in fact correct. In the context of testing protocols, model learning can be seen as a form of protocol state fuzzing, whereby unexpected inputs are sent to a system under test in the hope of exposing hidden anomalies. In model learning, inputs are sent with no regard to the order imposed by the protocol. Inputs can therefore be sent disorderly. Any anomalies are then exposed in the learned model.
Having obtained models, we use model checking to automatically verify their conformance to security properties. Such verification would be difficult manually, as the learned models are large. Moreover, the very satisfaction of properties can be used to refine the learned model. In cases where the learned model does not satisfy a security property, the proving sequence of inputs can either signal non-conformance of the system, or can be used to further refine the learned model. The security properties are drawn out of the RFC specifications\cite{rfc4251}\cite{rfc4252}\cite{rfc4253}\cite{rfc4254} and formalized in LTL. They are then checked for truth on the learned model using NuSMV~\cite{NuSMV}.
Our work is borne out of 2 recent theses \cite{Verleg2016}, \cite{Toon2016}. It most closely resembles work on TCP~\cite{Janssen2015Learning}, where they also combine classical learning and model checking to analyze various TCP implementations, with our work being more focused on security properties. Other case studies rely on
Our work is borne out of 2 recent theses \cite{Verleg2016}, \cite{Toon2016}. It most closely resembles work on TCP~\cite{TCP2016}, where they also combine classical learning and model checking to analyze various TCP implementations, with our work being more focused on security properties. Other case studies rely on
manual analysis of the learned models ~\cite{Aarts2013Formal},\cite{Chalupar2014Automated}, \cite{RuiterProtocol}. Our work differs,
since we use a model checker to automatically check specifications. %Moreover, the model checker is also used as means of testing the learned model.
Inference of protocols has also been done from observing network traffic ~\cite{Wang2011Inferring}. Such inference is limited to the traces that
......
......@@ -16,10 +16,22 @@ The learner uses LearnLib ~\cite{LearnLib2009}, a Java library implementing $L^{
\label{fig:components}
\end{figure}
%figure
SSH is a complex client-server type protocol. It would be exceedingly difficult to learn all its facets, thus we narrow down the learning goal to learning SSH
server implementations. We further restrict learning to only exploring the terminal service of the connection layer, as we consider it to be the most interesting
from a security perspective. Algorithms for encryption, compression and hashing are set to default settings and are not purposefully explored.
%figure
%It is therefore important to focus on messages for which interesting state-changing behaviour can be expected.
\subsection{The learning alphabet}
Learning time grows rapidly as the input alphabet grows. It is therefore important to focus on messages for which interesting state-changing behaviour can be expected. As a general principle, we therefore chose not to query protocol messages that are not intended to be sent from a client to a server\footnote{Applying this principle to the RFC's messages results in not including \textsc{service\_accept}, \textsc{ua\_accept}, \textsc{ua\_failure}, \textsc{ua\_banner}, \textsc{ua\_pk\_ok}, \textsc{ua\_pw\_changereq}, \textsc{ch\_success} and \textsc{ch\_failure} in our alphabet.}.
We split the learning alphabet into 3 groups, corresponding to the three layers. Each input in the alphabet has a corresponding message type in the SSH specification.
Learning doesn't scale with a growing alphabet, hence it is important to reduce the alphabet to those inputs that trigger interesting behavior. We do this by
only selecting inputs that are consistent with our learning goal.
Since we are learning only SSH server implementations, we filter out messages that were not intended to be sent to the server. \footnote{Applying this principle to
the RFC's messages results in not including \textsc{service\_accept}, \textsc{ua\_accept}, \textsc{ua\_failure}, \textsc{ua\_banner}, \textsc{ua\_pk\_ok}, \textsc{ua\_pw\_changereq}, \textsc{ch\_success} and \textsc{ch\_failure} in our alphabet.} Furthermore, from the Connection layer we only select general inputs plus those relating to the terminal functionality.
We reduce the alphabet further by only selecting inputs which follow the binary packet protocol, hence we don't include the identification input which should
be sent by both client and server at the start of every connection. The exchange of this inputs is made implicit. Finally, from the inputs defined, we make a selection
of essential inputs. These comprise the restricted alphabet, which we will use in some experiments.
Applying this ``outgoing only'' principle to the transport layer results in the messages of Table~\ref{trans-alphabet}. This is a special instance of \textsc{kexinit} for which \texttt{first\_kex\_packet\_follows} is enabled~\cite[p. 17]{rfc4253}. Our mapper can only handle correct key guesses, so the wrong-guess procedure as described in ~\cite[p. 19]{rfc4253} was not supported. When needed, SUTs were configured to make this guess work by altering their cipher preferences. The SSH version and comment string (described in Section~\ref{ssh-run-trans}) was not queried because it does not follow the binary packet protocol.
......@@ -40,7 +52,7 @@ Applying this ``outgoing only'' principle to the transport layer results in the
\textsc{sr\_auth} & Requests the authentication protocol~\cite[p. 23]{rfc4253} \\
\textsc{sr\_conn} & Requests the connection protocol~\cite[p. 23]{rfc4253}
\end{tabular}
\caption{Alphabet used to query the transport layer.}
\caption{Transport Layer inputs}
\label{trans-alphabet}
\end{table}
......@@ -59,7 +71,7 @@ As shown in Table~\ref{auth-alphabet}, both the public key as well as the passwo
\textsc{ua\_pw\_ok} & Provides a valid name/password combination~\cite[p. 10]{rfc4252} \\
\textsc{ua\_pw\_nok} & Provides an invalid name/password combination~\cite[p. 10]{rfc4252} \\
\end{tabular}
\caption{Alphabet used to query the user authentication layer.}
\caption{Authentication Layer inputs}
\label{auth-alphabet}
\end{table}
......@@ -78,7 +90,7 @@ The connection protocol allows the client to request different processes over a
\textsc{ch\_window\_adjust} & Adjusts the window size~\cite[p. 7]{rfc4254} \\
\textsc{ch\_request\_pty} & Requests terminal emulation~\cite[p. 11]{rfc4254} \\
\end{tabular}
\caption{Alphabet used to query the connection layer.}
\caption{Connection Layer inputs}
\label{conn-alphabet}
\end{table}
%The learning alphabet comprises of input/output messages by which the {\dlearner} interfaces with the {\dmapper}. Section~\ref{sec:ssh} outlines essential inputs, while Table X provides a summary
......@@ -87,29 +99,21 @@ The connection protocol allows the client to request different processes over a
%table
\subsection{The mapper}
The {\dmapper} must provide translation between abstract message representations and well-formed SSH messages. A special case is for when no output is received from the {\dsut}, in which case the {\dmapper} gives back to the learner a {\dtimeout} message, concluding a timeout occurred. The sheer complexity of the {\dmapper}, meant that it was easier to adapt an existing SSH implementation, rather than construct the {\dmapper} from scratch. Paramiko already provides routines for sending the different types of packets, and for receiving them. These routines are called by control logic dictated by Paramiko's own state machine. The {\dmapper} was constructed by replacing this control logic with one dictated by messages received from the {\dlearner}. %over a socket connection
The {\dmapper} must provide translation between abstract message representations and well-formed SSH messages. A special case is for when no output is received from the {\dsut}, in which case the {\dmapper} gives back to the learner a {\dtimeout} message, concluding a timeout occurred. The sheer complexity of the {\dmapper}, meant that it was easier to adapt an existing SSH implementation, rather than construct the {\dmapper} from scratch. Paramiko already provides mechanisms for encryption/decryption, as well as routines for sending the different types of packets, and for receiving them. These routines are called by control logic dictated by Paramiko's own state machine. The {\dmapper} was constructed by replacing this control logic with one dictated by messages received from the {\dlearner}. %over a socket connection
The {\dmapper} mapper maintains a set of state variables which are used in this abstract to concrete translation. It most notably, (1) saves the parameter preferences, (2)
The {\dmapper} maintains a set of state variables which have initial values, and which are updated on specific outputs and used in concretize certain inputs. On receiving a \textsc{kexinit}, the {\dmapper} saves the {\dsut}'s parameter preferences. These preferences define the key exchange, hashing and encryption algorithms supported by the {\dsut}. Before such receipt, these parameters are defaulted to those that any implementation should support, as required by the RFC. Based on these parameters, a key exchange algorithm may be run. The {\dmapper} supports Diffie-Hellman, which it can initiate via a \textsc{kex30} input from the learner. The {\dsut} responds with \textsc{kex31} if the inputs were orderly sent. From \textsc{kex31}, the {\dmapper} saves the hash, as well as the new keys. Receipt of the \textsc{newkeys} response from the {\dsut} will make the {\dmapper} use the new keys earlier negotiated in place of the older ones. The {\dmapper} contains two other state variables, used for storing the channel and sequence numbers respectively. The former is retrieved from a \textsc{ch\_accept} response and re-used in the other channel-type inputs, the latter each retrieved from each packet received and used in \textsc{unimpl} inputs. Both variables are initially set to 0.
it uses to craft concrete messages corresponding the learning abstractions.
%textbf{Message} & \textbf{Influence on mapper state} \\
%\textsc{kexinit} & Saves SUT's parameter preferences\tablefootnote{The parameters that must be supported according to the RFCs to ensure interoperability are used if no \textsc{kexinit} has been received.}. \\
%\textsc{kex31} & Saves the exchange hash resulting from key exchange. \\
%\textsc{newkeys} & Takes in use new keys for all outgoing messages\tablefootnote{Silently ignored when key exchange has not yet been completed.}. \\
%\textsc{ch\_accept} & Saves the channel identifier, used in some queries\tablefootnote{Zero is used if no \textsc{ch\_accept} has been received.}. \\
%\textit{any} & Saves the sequence number, used for the \textsc{unimpl} query\tablefootnote{Zero is used if no message has been received.}. \\
%\end{tabular}
%\caption{State-changing responses implemented by the mapper. These combinedly result in the mapper's state.}
%\label{table:state}
%\end{table}
In certain scenarios, inputs are answered by the {\dmapper} directly instead of being sent to the {\dsut}. These scenarios are the following:
\begin{enumerate}
\item connection with the {\dsut} was terminated, case in which the {\dmapper} responds with a \textsc{no\_conn} message
\item no channel has been opened or the maximum number of channels was reached (in our experiments 1), cases which prompt the {\dmapper}
to respond with \textsc{ch\_none}, and \textsc{ch\_max} respectively
\end{enumerate}
These are updated
The {\dmapper} updates its state with information from concrete messages. This enables it to craft concrete messages corresponding to the learner abstractions.
Overall, we notice that in many ways, the {\dmapper} acts similarly to an SSH client. Hence it is unsurprising that it was built off an existing
implementation.
%As and thus to interact with the {\dsut}, at times similar to how an SSH client would.
%, sourced from interacting with the {\dsut}.
\subsection{Compacting SSH into a small Mealy machine}
The {\dmapper} not only provides abstraction, it also ensures that the abstract representation shown to the learner behaves like a deterministic Mealy Machine.
......
This diff is collapsed.
@misc{NuSMV,
title = {The NuSMV Model Checker},
howpublished = {\url{http://nusmv.fbk.eu/}},
howpublished = "\url{http://nusmv.fbk.eu/}",
key={NuSMV},
year={2016}
}
@phdthesis{Toon2016,
@article{LearnLib2009,
author = {H. Raffelt and B. Steffen and T. Berg and T. Margaria},
title = {{LearnLib}: a framework for extrapolating behavioral models},
journal = {STTT},
volume = {11},
number = {5},
year = {2009},
pages = {393-407}
}
@article{LeeY96,
author = {D. Lee and M. Yannakakis},
year = 1996,
journal = {Proceedings of the IEEE},
volume = 84,
number = 8,
pages = {1090--1123},
title = {Principles and methods of testing finite state machines --- a survey}
}
@MastersThesis{Toon2016,
title={Improving Protocol State Fuzzing of SSH},
author={Lenaerts, T and Vaandrager, FW and Fiterau-Brostean, P and Poll, E},
year={2016}
author={Lenaerts, T},
year={2016},
document_type = {Bachelor's Thesis},
type = {Bachelor's Thesis},
howpublished = {Online},
journal = {Radboud University}
}
@phdthesis{Verleg2016,
@phdthesis{Isberner2015,
author = {Malte Isberner},
year = 2015,
school = {Technical University of Dortmund},
title = {Foundations of Active Automata Learning: An Algorithmic Perspective}
}
@MastersThesis{Verleg2016,
title={Inferring SSH state machines using protocol state fuzzing},
author={Verleg, P and Poll, E and Vaandrager, FW},
year={2016}
author={Verleg, P},
year={2016},
howpublished = {Online},
journal = {Radboud University}
}
@article{rfc760,
......@@ -328,16 +363,21 @@ machine learning algorithms},
year = {2013}
}
@mastersthesis{Janssen2015Learning,
author = {Janssen, Ramon},
citeulike-article-id = {13779542},
howpublished = {Online},
journal = {Radboud University},
posted-at = {2015-09-30 18:55:55},
priority = {2},
school = {Radboud University},
title = {Learning a State Diagram of {TCP} Using Abstraction.},
year = {2015}
@Inbook{TCP2016,
author="Fiter{\u{a}}u-Bro{\c{s}}tean, Paul
and Janssen, Ramon
and Vaandrager, Frits",
editor="Chaudhuri, Swarat
and Farzan, Azadeh",
title="Combining Model Learning and Model Checking to Analyze TCP Implementations",
bookTitle="Computer Aided Verification: 28th International Conference, CAV 2016, Toronto, ON, Canada, July 17-23, 2016, Proceedings, Part II",
year="2016",
publisher="Springer International Publishing",
address="Cham",
pages="454--471",
isbn="978-3-319-41540-6",
doi="10.1007/978-3-319-41540-6_25",
url="http://dx.doi.org/10.1007/978-3-319-41540-6_25"
}
@electronic{WheelerHow,
......@@ -503,89 +543,3 @@ machine learning algorithms},
year = {1997}
}
@ARTICLE{bowman:reasoning,
author = {Bowman, Mic and Debray, Saumya K. and Peterson, Larry L.},
title = {Reasoning About Naming Systems},
journal = {ACM Trans. Program. Lang. Syst.},
volume = {15},
number = {5},
pages = {795-825},
month = {November},
year = {1993},
doi = {10.1145/161468.161471},
}
@ARTICLE{braams:babel,
author = {Braams, Johannes},
title = {Babel, a Multilingual Style-Option System for Use with LaTeX's Standard Document Styles},
journal = {TUGboat},
volume = {12},
number = {2},
pages = {291-301},
month = {June},
year = {1991},
}
@INPROCEEDINGS{clark:pct,
AUTHOR = "Malcolm Clark",
TITLE = "Post Congress Tristesse",
BOOKTITLE = "TeX90 Conference Proceedings",
PAGES = "84-89",
ORGANIZATION = "TeX Users Group",
MONTH = "March",
YEAR = {1991} }
@ARTICLE{herlihy:methodology,
author = {Herlihy, Maurice},
title = {A Methodology for Implementing Highly Concurrent Data Objects},
journal = {ACM Trans. Program. Lang. Syst.},
volume = {15},
number = {5},
pages = {745-770},
month = {November},
year = {1993},
doi = {10.1145/161468.161469},
}
@BOOK{Lamport:LaTeX,
AUTHOR = "Leslie Lamport",
TITLE = "LaTeX User's Guide and Document Reference Manual",
PUBLISHER = "Addison-Wesley Publishing Company",
ADDRESS = "Reading, Massachusetts",
YEAR = "1986" }
@BOOK{salas:calculus,
AUTHOR = "S.L. Salas and Einar Hille",
TITLE = "Calculus: One and Several Variable",
PUBLISHER = "John Wiley and Sons",
ADDRESS = "New York",
YEAR = "1978" }
@MANUAL{Fear05,
title = {Publication quality tables in {\LaTeX}},
author = {Simon Fear},
month = {April},
year = 2005,
note = {\url{http://www.ctan.org/pkg/booktabs}}
}
@Manual{Amsthm15,
title = {Using the amsthm Package},
organization = {American Mathematical Society},
month = {April},
year = 2015,
note = {\url{http://www.ctan.org/pkg/amsthm}}
}
Here we add specifications that might be model checked.
trans p 24
key exchange does not affect the protocols that lie
above the SSH transport layer.
trans, p 24
If the server rejects the service request, it SHOULD send an
appropriate SSH_MSG_DISCONNECT message and MUST disconnect.
After the SSH_MSG_KEXINIT message exchange, the key exchange
algorithm is run. It may involve several packet exchanges, as
specified by the key exchange method.
Once a party has sent a SSH_MSG_KEXINIT message for key exchange or
re-exchange, until it has sent a SSH_MSG_NEWKEYS message (Section
7.3), it MUST NOT send any messages other than:
o Transport layer generic messages (1 to 19) (but
SSH_MSG_SERVICE_REQUEST and SSH_MSG_SERVICE_ACCEPT MUST NOT be
sent);
o Algorithm negotiation messages (20 to 29) (but further
SSH_MSG_KEXINIT messages MUST NOT be sent);(
auth, p 4
If the server rejects the authentication request, it MUST respond
with the following:
byte SSH_MSG_USERAUTH_FAILURE
name-list authentications that can continue
boolean partial success
auth, p 5
A request that requires further messages to be exchanged will be
aborted by a subsequent request. A client MUST NOT send a subsequent
request if it has not received a response from the server for a
previous request. A SSH_MSG_USERAUTH_FAILURE message MUST NOT be
sent for an aborted method.
SSH_MSG_USERAUTH_SUCCESS MUST be sent only once. When
SSH_MSG_USERAUTH_SUCCESS has been sent, any further authentication
requests received after that SHOULD be silently ignored.
conn, p 9
SSH_MSG_CHANNEL_CLOSE
Connection Layer:
......@@ -12,41 +58,99 @@ Upon receiving this message, a party MUST
SSH_MSG_CHANNEL_CLOSE without having sent or received
SSH_MSG_CHANNEL_EOF.
If the request is not recognized or is not
supported for the channel, SSH_MSG_CHANNEL_FAILURE is returned.
conn p 9
Once a party has sent a SSH_MSG_KEXINIT message for key exchange or
re-exchange, until it has sent a SSH_MSG_NEWKEYS message (Section
7.3), it MUST NOT send any messages other than:
5.4. Channel-Specific Requests
o Transport layer generic messages (1 to 19) (but
SSH_MSG_SERVICE_REQUEST and SSH_MSG_SERVICE_ACCEPT MUST NOT be
sent);
Many 'channel type' values have extensions that are specific to that
particular 'channel type'. An example is requesting a pty (pseudo
terminal) for an interactive session.
o Algorithm negotiation messages (20 to 29) (but further
SSH_MSG_KEXINIT messages MUST NOT be sent);
All channel-specific requests use the following format.
o Specific key exchange method messages (30 to 49).
byte SSH_MSG_CHANNEL_REQUEST
uint32 recipient channel
string request type in US-ASCII characters only
boolean want reply
.... type-specific data follows
If 'want reply' is FALSE, no response will be sent to the request.
Otherwise, the recipient responds with either
SSH_MSG_CHANNEL_SUCCESS, SSH_MSG_CHANNEL_FAILURE, or request-specific
continuation messages. If the request is not recognized or is not
supported for the channel, SSH_MSG_CHANNEL_FAILURE is returned.
This message does not consume window space and can be sent even if no
window space is available. The values of 'request type' are local to
each channel type.
The client is allowed to send further messages without waiting for
the response to the request.
'request type' names follow the DNS extensibility naming convention
outlined in [SSH-ARCH] and [SSH-NUMBERS].
If the server rejects the service request, it SHOULD send an
appropriate SSH_MSG_DISCONNECT message and MUST disconnect.
byte SSH_MSG_CHANNEL_SUCCESS
uint32 recipient channel
byte SSH_MSG_CHANNEL_FAILURE
uint32 recipient channel
After the SSH_MSG_KEXINIT message exchange, the key exchange
algorithm is run. It may involve several packet exchanges, as
specified by the key exchange method.
These messages do not consume window space and can be sent even if no
window space is available.
Once a party has sent a SSH_MSG_KEXINIT message for key exchange or
re-exchange, until it has sent a SSH_MSG_NEWKEYS message (Section
7.3), it MUST NOT send any messages other than:
o Transport layer generic messages (1 to 19) (but
SSH_MSG_SERVICE_REQUEST and SSH_MSG_SERVICE_ACCEPT MUST NOT be
sent);
o Algorithm negotiation messages (20 to 29) (but further
SSH_MSG_KEXINIT messages MUST NOT be sent);(
== unsure about ==
trans, p 24
Key re-exchange is started by sending an SSH_MSG_KEXINIT packet when
not already doing a key exchange (as described in Section 7.1). When
this message is received, a party MUST respond with its own
SSH_MSG_KEXINIT message, except when the received SSH_MSG_KEXINIT
already was a reply. Either party MAY initiate the re-exchange, but
roles MUST NOT be changed (i.e., the server remains the server, and
the client remains the client).
== note that ==
trans, p 25
disconnect (as well as other messages allowed at any time)
11. Additional Messages
Either party may send any of the following messages at any time.
11.1. Disconnection Message
byte SSH_MSG_DISCONNECT
uint32 reason code
string description in ISO-10646 UTF-8 encoding [RFC3629]
string language tag [RFC3066]
This message causes immediate termination of the connection. All
implementations MUST be able to process this message; they SHOULD be
able to send this message.
The sender MUST NOT send or receive any data after this message, and
the recipient MUST NOT accept any data after receiving this message.
The Disconnection Message 'description' string gives a more specific
explanation in a human-readable form. The Disconnection Message
'reason code' gives the reason in a more machine-readable format
(suitable for localization), and can have the values as displayed in
the table below. Note that the decimal representation is displayed
in this table for readability, but the values are actually uint32
values.
11.4. Reserved Messages
An implementation MUST respond to all unrecognized messages with an
SSH_MSG_UNIMPLEMENTED message in the order in which the messages were
received. Such messages MUST be otherwise ignored. Later protocol
versions may define other meanings for these message types.
byte SSH_MSG_UNIMPLEMENTED
uint32 packet sequence number of rejected message
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment