use constants::ASKLEN;
use error::{SnowError, StateProblem};
use handshakestate::HandshakeState;
#[cfg(feature = "nightly")] use std::convert::{TryFrom, TryInto};
#[cfg(not(feature = "nightly"))] use utils::{TryFrom, TryInto};
use transportstate::TransportState;
use stateless_transportstate::StatelessTransportState;
/// A state machine for the entire Noise session.
///
/// Enums provide a convenient interface as it's how Rust implements union structs, meaning this is
/// a sized object.
// TODO: check up on memory usage, since this clippy warning seems like a legit perf issue.
#[cfg_attr(feature = "cargo-clippy", allow(large_enum_variant))]
#[derive(Debug)]
pub enum Session {
/// A session in the handshake stage (the starting state).
Handshake(HandshakeState),
/// A session after having completed a handshake, in general-purpose trasport mode.
Transport(TransportState),
/// A session after having completed a handshake, in explicit-nonce trasport mode.
StatelessTransport(StatelessTransportState),
}
impl Session {
/// This method will return `true` if the *previous* write payload was encrypted.
///
/// See [Payload Security Properties](http://noiseprotocol.org/noise.html#payload-security-properties)
/// for more information on the specific properties of your chosen handshake pattern.
///
/// # Examples
///
/// ```rust,ignore
/// let mut session = Builder::new("Noise_NN_25519_AESGCM_SHA256".parse()?)
/// .build_initiator()?;
///
/// // write message...
///
/// assert!(session.was_write_payload_encrypted());
/// ```
pub fn was_write_payload_encrypted(&self) -> bool {
match *self {
Session::Handshake(ref state) => state.was_write_payload_encrypted(),
Session::Transport(_) => true,
Session::StatelessTransport(_) => true,
}
}
/// True if the handshake is finished and the Session state machine is ready to be transitioned
/// to transport mode. This function also returns a vacuous true if already in transport mode.
///
/// # Examples
///
/// ```rust,ignore
/// let mut session = Builder::new("Noise_NN_25519_AESGCM_SHA256".parse()?)
/// .build_initiator()?;
///
/// if (session.is_handshake_finished()) {
/// session = session.into_transport_mode()?;
/// }
/// ```
pub fn is_handshake_finished(&self) -> bool {
match *self {
Session::Handshake(ref state) => state.is_finished(),
Session::Transport(_) => true,
Session::StatelessTransport(_) => true,
}
}
/// Will report if the session has the initiator role (i.e. was built with [`Builder.build_initiator()`]).
///
/// [`Builder.build_initiator()`]: struct.Builder.html#method.build_initiator
pub fn is_initiator(&self) -> bool {
match *self {
Session::Handshake(ref state) => state.is_initiator(),
Session::Transport(ref state) => state.is_initiator(),
Session::StatelessTransport(ref state) => state.is_initiator(),
}
}
/// Construct a message from `payload` (and pending handshake tokens if in handshake state),
/// and writes it to the `output` buffer.
///
/// Returns the size of the written payload.
///
/// # Errors
///
/// Will result in `SnowError::Input` if the size of the output exceeds the max message
/// length in the Noise Protocol (65535 bytes).
#[must_use]
pub fn write_message(&mut self, payload: &[u8], output: &mut [u8]) -> Result<usize, SnowError> {
match *self {
Session::Handshake(ref mut state) => state.write_handshake_message(payload, output),
Session::Transport(ref mut state) => state.write_transport_message(payload, output),
Session::StatelessTransport(_) => bail!(StateProblem::StatelessTransportMode),
}
}
/// Construct a message from `payload` with an explicitly provided nonce and write it to the
/// `output` buffer.
///
/// Returns the size of the written payload.
///
/// # Errors
///
/// Will result in `SnowError::Input` if the size of the output exceeds the max message
/// length in the Noise Protocol (65535 bytes).
///
/// Will result in `SnowError::StateProblem` if not in stateless transport mode.
#[must_use]
pub fn write_message_with_nonce(&self, nonce: u64, payload: &[u8], output: &mut [u8]) -> Result<usize, SnowError> {
match *self {
Session::StatelessTransport(ref state) => state.write_transport_message(nonce, payload, output),
_ => bail!(StateProblem::StatelessTransportMode),
}
}
/// Reads a noise message from `input`
///
/// Returns the size of the payload written to `payload`.
///
/// # Errors
///
/// Will result in `SnowError::Decrypt` if the contents couldn't be decrypted and/or the
/// authentication tag didn't verify.
///
/// # Panics
///
/// This function will panic if there is no key, or if there is a nonce overflow.
#[must_use]
pub fn read_message(&mut self, input: &[u8], payload: &mut [u8]) -> Result<usize, SnowError> {
match *self {
Session::Handshake(ref mut state) => state.read_handshake_message(input, payload),
Session::Transport(ref mut state) => state.read_transport_message(input, payload),
Session::StatelessTransport(_) => bail!(StateProblem::StatelessTransportMode),
}
}
/// Construct a message from `payload` (and pending handshake tokens if in handshake state),
/// and writes it to the `output` buffer.
///
/// Returns the size of the written payload.
///
/// # Errors
///
/// Will result in `SnowError::Input` if the size of the output exceeds the max message
/// length in the Noise Protocol (65535 bytes).
///
/// Will result in `SnowError::StateProblem` if not in stateless transport mode.
#[must_use]
pub fn read_message_with_nonce(&self, nonce: u64, input: &[u8], payload: &mut [u8]) -> Result<usize, SnowError> {
match *self {
Session::StatelessTransport(ref state) => state.read_transport_message(nonce, input, payload),
_ => bail!(StateProblem::StatelessTransportMode),
}
}
/// Generates a new key for the egress symmetric cipher according to Section 4.2
/// of the Noise Specification. Synchronizing timing of rekey between initiator and
/// responder is the responsibility of the application, as described in Section 11.3
/// of the Noise Specification.
///
/// # Errors
///
/// Will result in `SnowError::State` if not in transport mode.
#[must_use]
pub fn rekey_outgoing(&mut self) -> Result<(), SnowError> {
match *self {
Session::Handshake(_) => bail!(StateProblem::HandshakeNotFinished),
Session::Transport(ref mut state) => {
state.rekey_outgoing();
Ok(())
},
Session::StatelessTransport(ref mut state) => {
state.rekey_outgoing();
Ok(())
},
}
}
/// Generates a new key for the ingress symmetric cipher according to Section 4.2
/// of the Noise Specification. Synchronizing timing of rekey between initiator and
/// responder is the responsibility of the application, as described in Section 11.3
/// of the Noise Specification.
///
/// # Errors
///
/// Will result in `SnowError::State` if not in transport mode.
#[must_use]
pub fn rekey_incoming(&mut self) -> Result<(), SnowError> {
match *self {
Session::Handshake(_) => bail!(StateProblem::HandshakeNotFinished),
Session::Transport(ref mut state) => {
state.rekey_incoming();
Ok(())
},
Session::StatelessTransport(ref mut state) => {
state.rekey_incoming();
Ok(())
},
}
}
/// Set a new key for the one or both of the initiator-egress and responder-egress symmetric ciphers.
///
/// # Errors
///
/// Will result in `SnowError::State` if not in transport mode.
#[must_use]
pub fn rekey_manually(&mut self, initiator: Option<&[u8]>, responder: Option<&[u8]>) -> Result<(), SnowError> {
match *self {
Session::Handshake(_) => bail!(StateProblem::HandshakeNotFinished),
Session::Transport(ref mut state) => {
if let Some(key) = initiator {
state.rekey_initiator_manually(key);
}
if let Some(key) = responder {
state.rekey_responder_manually(key);
}
Ok(())
},
Session::StatelessTransport(ref mut state) => {
if let Some(key) = initiator {
state.rekey_initiator_manually(key);
}
if let Some(key) = responder {
state.rekey_responder_manually(key);
}
Ok(())
},
}
}
/// Get the forthcoming inbound nonce value.
///
/// # Errors
///
/// Will result in `SnowError::State` if not in transport mode.
pub fn receiving_nonce(&self) -> Result<u64, SnowError> {
match *self {
Session::Handshake(_) => bail!(StateProblem::HandshakeNotFinished),
Session::Transport(ref state) => Ok(state.receiving_nonce()),
Session::StatelessTransport(_) => bail!(StateProblem::StatelessTransportMode),
}
}
/// Get the forthcoming outbound nonce value.
///
/// # Errors
///
/// Will result in `SnowError::State` if not in transport mode.
pub fn sending_nonce(&self) -> Result<u64, SnowError> {
match *self {
Session::Handshake(_) => bail!(StateProblem::HandshakeNotFinished),
Session::Transport(ref state) => Ok(state.sending_nonce()),
Session::StatelessTransport(_) => bail!(StateProblem::StatelessTransportMode),
}
}
/// Get the remote static key that was possibly encrypted in the first payload.
///
/// Returns a slice of length `Dh.pub_len()` (i.e. DHLEN for the chosen DH function).
pub fn get_remote_static(&self) -> Option<&[u8]> {
match *self {
Session::Handshake(ref state) => state.get_remote_static(),
Session::Transport(ref state) => state.get_remote_static(),
Session::StatelessTransport(ref state) => state.get_remote_static(),
}
}
/// Get the handshake hash.
///
/// Returns a slice of length `Hasher.hash_len()` (i.e. HASHLEN for the chosen Hash function).
pub fn get_handshake_hash(&self) -> Result<&[u8], SnowError> {
match *self {
Session::Handshake(ref state) => Ok(state.get_handshake_hash()),
_ => bail!(StateProblem::HandshakeAlreadyFinished),
}
}
/// Set the preshared key at the specified location. It is up to the caller
/// to correctly set the location based on the specified handshake - Snow
/// won't stop you from placing a PSK in an unused slot.
///
/// # Errors
///
/// Will result in `SnowError::Input` if the PSK is not the right length or the location is out of bounds.
/// Will result in `SnowError::State` if in transport mode.
#[must_use]
pub fn set_psk(&mut self, location: usize, key: &[u8]) -> Result<(), SnowError> {
match *self {
Session::Handshake(ref mut state) => state.set_psk(location, key),
_ => bail!(StateProblem::HandshakeAlreadyFinished)
}
}
/// Set the data to be authenticated at the specified location. It is up to
/// the caller to correctly set the location based on the specified
/// handshake - Snow won't stop you from placing data in an unused slot.
///
/// # Errors
///
/// Will result in `SnowError::Input` if the location is out of bounds.
/// Will result in `SnowError::StateError` if in transport mode.
pub fn set_h_data(&mut self, location: usize, data: &[u8]) -> Result<(), SnowError> {
match *self {
Session::Handshake(ref mut state) => state.set_h_data(location, data),
_ => bail!(StateProblem::HandshakeAlreadyFinished),
}
}
/// Initialize the ASK chains with the given labels.
///
/// # Errors
///
/// Will result in `SnowError::State` if ASK is not enabled, or the ASK master key is not available.
pub fn initialize_ask(&mut self, labels: Vec<String>) -> Result<(), SnowError> {
match *self {
Session::Handshake(ref mut state) => state.initialize_ask(labels),
_ => bail!(StateProblem::HandshakeAlreadyFinished),
}
}
/// Get the next key from the key chain corresponding to the given label.
///
/// # Errors
///
/// Will result in `SnowError::State` if ASK is not enabled, `initialize_ask()` has not been called,
/// or if the key chain has already been finalized.
/// Will result in `SnowError::Input` if the label was not provided to `initialize_ask()`.
pub fn get_ask(&mut self, label: &String) -> Result<[u8; ASKLEN], SnowError> {
match *self {
Session::Handshake(ref mut state) => state.get_ask(label),
_ => bail!(StateProblem::HandshakeAlreadyFinished),
}
}
/// Get the last two keys from the key chain corresponding to the given label, and finalizes it.
///
/// # Errors
///
/// Will result in `SnowError::State` if ASK is not enabled, or `initialize_ask()` has not been called,
/// or if the key chain has already been finalized.
/// Will result in `SnowError::Input` if the label was not provided to `initialize_ask()`.
pub fn finalize_ask(&mut self, label: &String) -> Result<([u8; ASKLEN], [u8; ASKLEN]), SnowError> {
match *self {
Session::Handshake(ref mut state) => state.finalize_ask(label),
_ => bail!(StateProblem::HandshakeAlreadyFinished),
}
}
/// Transition the session into transport mode. This can only be done once the handshake
/// has finished.
///
/// Consumes the previous state, and returns the new transport state object, thereby freeing
/// any material only used during the handshake phase.
///
/// # Errors
///
/// Will result in `SnowError::State` if the handshake is not finished.
///
/// # Examples
///
/// ```rust,ignore
/// let mut session = Builder::new("Noise_NN_25519_AESGCM_SHA256".parse()?)
/// .build_initiator()?;
///
/// // ... complete handshake ...
///
/// session = session.into_transport_mode()?;
/// ```
///
pub fn into_transport_mode(self) -> Result<Self, SnowError> {
match self {
Session::Handshake(state) => {
if !state.is_finished() {
bail!(StateProblem::HandshakeNotFinished)
} else {
Ok(Session::Transport(state.try_into()?))
}
},
_ => Ok(self)
}
}
/// Transition the session into stateless (explicit nonce) transport mode.
/// This is useful when using Noise over lossy transports.
/// Like `into_transport_mode()`, this can only be done once the handshake has finished.
///
/// Consumes the previous state, and returns the new transport state object, thereby freeing
/// any material only used during the handshake phase.
///
/// # Errors
///
/// Will result in `SnowError::State` if the handshake is not finished.
///
/// # Examples
///
/// ```rust,ignore
/// let mut session = Builder::new("Noise_NN_25519_AESGCM_SHA256".parse()?)
/// .build_initiator()?;
///
/// // ... complete handshake ...
///
/// session = session.into_stateless_transport_mode()?;
/// ```
///
pub fn into_stateless_transport_mode(self) -> Result<Self, SnowError> {
match self {
Session::Handshake(state) => {
if !state.is_finished() {
bail!(StateProblem::HandshakeNotFinished)
} else {
Ok(Session::StatelessTransport(state.try_into()?))
}
},
_ => Ok(self)
}
}
}
impl Into<Session> for HandshakeState {
fn into(self) -> Session {
Session::Handshake(self)
}
}
impl TryFrom<HandshakeState> for TransportState {
type Error = SnowError;
fn try_from(old: HandshakeState) -> Result<Self, Self::Error> {
TransportState::new(old)
}
}
impl TryFrom<HandshakeState> for StatelessTransportState {
type Error = SnowError;
fn try_from(old: HandshakeState) -> Result<Self, Self::Error> {
StatelessTransportState::new(old)
}
}