Class NoiseHandshake

java.lang.Object
com.eatthepath.noise.NoiseHandshake

public class NoiseHandshake extends Object

A NoiseHandshake instance is responsible for encrypting and decrypting the messages that comprise a Noise handshake. Once a Noise handshake instance has finished exchanging handshake messages, it can produce a Noise transport object for steady-state encryption and decryption of Noise transport messages.

Noise handshake messages contain key material and an optional payload. The security properties for the optional payload vary by handshake pattern, message, and sender role. Callers are responsible for verifying that the security properties associated with ny handshake message are suitable for their use case. Please see The Noise Protocol Framework - Payload security properties for a complete explanation.

Generally speaking, the initiator and responder alternate sending and receiving messages until all messages in the handshake pattern have been exchanged. At that point, callers transform (or "split" in the terminology of the Noise Protocol Framework specification) the Noise handshake into a Noise transport instance appropriate for the handshake type (i.e. one-way or interactive) and pass Noise transport messages between the initiator and responder as needed.

Noise handshake instances are stateful and are not thread-safe.

Interactive patterns

In the most common case, Noise handshakes implement a interactive pattern in which both parties will send and receive messages to one another once the handshake is complete. As an example, the NN interactive handshake pattern is defined as:

NN:
   -> e
   <- e, ee

The parties in an NN handshake exchange messages until all required messages have been exchanged, then the handshake instances yield interactive transport instances:

final NoiseHandshake initiatorHandshake = NoiseHandshakeBuilder.forNNInitiator()
    .setComponentsFromProtocolName("Noise_NN_25519_AESGCM_SHA256")
    .build();

final NoiseHandshake responderHandshake = NoiseHandshakeBuilder.forNNResponder()
    .setComponentsFromProtocolName("Noise_NN_25519_AESGCM_SHA256")
    .build();

// -> e (with an empty payload)
final byte[] initiatorEMessage = initiatorHandshake.writeMessage((byte[]) null);
responderHandshake.readMessage(initiatorEMessage);

// <- e, ee (with an empty payload)
final byte[] responderEEeMessage = responderHandshake.writeMessage((byte[]) null);
initiatorHandshake.readMessage(responderEEeMessage);

assert initiatorHandshake.isDone();
assert responderHandshake.isDone();

final NoiseTransport initiatorTransport = initiatorHandshake.toTransport();
final NoiseTransport responderTransport = responderHandshake.toTransport();

One-way patterns

Noise handshakes may also use one-way patterns. As the Noise Protocol Framework specification notes:

These patterns could be used to encrypt files, database records, or other non-interactive data streams.

One-way handshakes exchange handshake messages in the same way as interactive handshakes, but instead of producing interactive NoiseTransport instances, one-way handshakes produce a one-way NoiseTransportWriter for initiators or NoiseTransportReader for responders. As an example, the N handshake pattern is defined as:

N:
   <- s
   ...
   -> e, es

The parties in an N handshake exchange messages as usual, then the handshake instances yield one-way transport instances:

final NoiseHandshake initiatorHandshake = NoiseHandshakeBuilder.forNInitiator(responderStaticPublicKey)
    .setComponentsFromProtocolName("Noise_N_25519_AESGCM_SHA256")
    .build();

final NoiseHandshake responderHandshake = NoiseHandshakeBuilder.forNResponder(responderStaticKeyPair)
    .setComponentsFromProtocolName("Noise_N_25519_AESGCM_SHA256")
    .build();

// -> e, es (with an empty payload)
final byte[] initiatorEphemeralKeyMessage = initiatorHandshake.writeMessage((byte[]) null);
responderHandshake.readMessage(initiatorEphemeralKeyMessage);

assert initiatorHandshake.isDone();
assert responderHandshake.isDone();

final NoiseTransportWriter transportWriter = initiatorHandshake.toTransportWriter();
final NoiseTransportReader transportReader = responderHandshake.toTransportReader();

Fallback patterns

Noise handshakes can "fall back" to another pattern to handle certain kinds of errors. As an example, the Noise Pipes compound protocol expects that initiators will usually have the responder's static public key available from a previous "full" (XX) handshake, and can use an abbreviated (IK) handshake pattern with that static key set via a pre-handshake message. If the responder can't decrypt a message from the initiator, though, it might conclude that the initiator has a stale copy of its public key and can fall back to a "full" (XXfallback) handshake.

The IK handshake pattern is defined as:

IK:
   <- s
   ...
   -> e, es, s, ss
   <- e, ee, se

…and the XXfallback pattern is defined as:

XXfallback:
   -> e
   ...
   <- e, ee, s, es
   -> s, se

As an example, consider a scenario where the initiator of an IK handshake has a "stale" static key for the responder:

final NoiseHandshake ikInitiatorHandshake =
    NoiseHandshakeBuilder.forIKInitiator(initiatorStaticKeyPair, staleRemoteStaticPublicKey)
        .setComponentsFromProtocolName("Noise_IK_25519_AESGCM_SHA256")
        .build();

final NoiseHandshake ikResponderHandshake =
    NoiseHandshakeBuilder.forIKResponder(currentResponderStaticKeyPair)
        .setComponentsFromProtocolName("Noise_IK_25519_AESGCM_SHA256")
        .build();

The initiator sends its first message to the responder, which won't be able to decrypt the message due to the static key disagreement:

// -> e, es, s, ss (with an empty payload)
final byte[] initiatorStaticKeyMessage = ikInitiatorHandshake.writeMessage((byte[]) null);

// Throws an AEADBadTagException because the initiator has a stale static key for the responder
ikResponderHandshake.readMessage(initiatorStaticKeyMessage);

Rather than simply failing the handshake (assuming both the initiator and responder are expecting that a fallback may happen), the responder can fall back to the XXfallback pattern, reusing the ephemeral key it already received from the initiator as a pre-handshake message, and write a message to continue the XXfallback pattern:

final NoiseHandshake xxFallbackResponderHandshake =
    ikResponderHandshake.fallbackTo("XXfallback");

// <- e, ee, s, es (with an empty payload)
final byte[] responderStaticKeyMessage = xxFallbackResponderHandshake.writeMessage((byte[]) null);

The initiator will fail to decrypt the message from the responder:

// Throws an AEADBadTagException
ikInitiatorHandshake.readMessage(responderStaticKeyMessage);

Like the responder, the initiator can take the decryption failure as a cue to fall back to the XXfallback pattern, then read the message and finish the handshake:

final NoiseHandshake xxFallbackInitiatorHandshake =
    ikInitiatorHandshake.fallbackTo("XXfallback");

xxFallbackInitiatorHandshake.readMessage(responderStaticKeyMessage);

final byte[] initiatorFallbackStaticKeyMessage =
    xxFallbackInitiatorHandshake.writeMessage((byte[]) null);

xxFallbackResponderHandshake.readMessage(initiatorFallbackStaticKeyMessage);

assert xxFallbackInitiatorHandshake.isDone();
assert xxFallbackResponderHandshake.isDone();

Once the handshake is finished, the transition to the transport phase of the protocol continues as usual.

See Also:
  • Nested Class Summary

    Nested Classes
    Modifier and Type
    Class
    Description
    static enum 
    An enumeration of roles within a Noise handshake.
  • Method Summary

    Modifier and Type
    Method
    Description
    fallbackTo(String handshakePatternName)
    "Falls back" to the named handshake pattern, transferring any appropriate static/ephemeral keys and an empty collection of pre-shared keys.
    fallbackTo(String handshakePatternName, List<byte[]> preSharedKeys)
    "Falls back" to the named handshake pattern, transferring any appropriate static/ephemeral keys and the given collection of pre-shared keys.
    byte[]
    Returns a hash of this handshake's state that uniquely identifies the Noise session.
    Returns the full name of the Noise protocol for this handshake.
    int
    getOutboundMessageLength(int payloadLength)
    Returns the length of the Noise handshake message this handshake would produce for a payload with the given length and with this handshake's current state.
    int
    getPayloadLength(int handshakeMessageLength)
    Returns the length of the plaintext of a payload contained in a Noise handshake message of the given length and with this handshake's current state.
    boolean
    Checks if this handshake has successfully exchanged all messages required by its handshake pattern.
    boolean
    Checks if this handshake is currently expecting to receive a handshake message from its peer.
    boolean
    Checks if this handshake is currently expecting to send a handshake message to its peer.
    boolean
    Checks whether this is a handshake for a one-way Noise handshake pattern.
    byte[]
    readMessage(byte[] message)
    Reads the next handshake message, advancing this handshake's internal state.
    int
    readMessage(byte[] message, int messageOffset, int messageLength, byte[] payload, int payloadOffset)
    Reads the next handshake message, writing the plaintext of the message's payload into the given array and advancing this handshake's internal state.
    Reads the next handshake message, advancing this handshake's internal state.
    int
    readMessage(ByteBuffer message, ByteBuffer payload)
    Reads the next handshake message, writing the plaintext of the message's payload into the given buffer and advancing this handshake's internal state.
    Builds a interactive Noise transport object from this handshake.
    Builds a read-only Noise transport object from this handshake.
    Builds a write-only Noise transport object from this handshake.
    byte[]
    writeMessage(byte[] payload)
    Writes the next Noise handshake message for this handshake instance, advancing this handshake's internal state.
    int
    writeMessage(byte[] payload, int payloadOffset, int payloadLength, byte[] message, int messageOffset)
    Writes the next Noise handshake message for this handshake instance into the given array, advancing this handshake's internal state.
    Writes the next Noise handshake message for this handshake instance, advancing this handshake's internal state.
    int
    writeMessage(ByteBuffer payload, ByteBuffer message)
    Writes the next Noise handshake message for this handshake instance into the given buffer, advancing this handshake's internal state.

    Methods inherited from class java.lang.Object

    clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
  • Method Details

    • getNoiseProtocolName

      public String getNoiseProtocolName()
      Returns the full name of the Noise protocol for this handshake.
      Returns:
      the full name of the Noise protocol for this handshake
      See Also:
    • isOneWayHandshake

      public boolean isOneWayHandshake()
      Checks whether this is a handshake for a one-way Noise handshake pattern.
      Returns:
      true if this is a handshake for a one-way Noise handshake pattern or false if this is a handshake for a interactive Noise handshake pattern
    • isExpectingRead

      public boolean isExpectingRead()
      Checks if this handshake is currently expecting to receive a handshake message from its peer.
      Returns:
      true if this handshake is expecting to receive a handshake message from its peer as its next action or false if this handshake is done or is expecting to send a handshake message to its peer as its next action
      See Also:
    • isExpectingWrite

      public boolean isExpectingWrite()
      Checks if this handshake is currently expecting to send a handshake message to its peer.
      Returns:
      true if this handshake is expecting to send a handshake message to its peer as its next action or false if this handshake is done or is expecting to receive a handshake message from its peer as its next action
      See Also:
    • isDone

      public boolean isDone()
      Checks if this handshake has successfully exchanged all messages required by its handshake pattern.
      Returns:
      true if all required messages have been exchanged or false if more exchanges are required
      See Also:
    • getOutboundMessageLength

      public int getOutboundMessageLength(int payloadLength)
      Returns the length of the Noise handshake message this handshake would produce for a payload with the given length and with this handshake's current state.
      Parameters:
      payloadLength - the length of a payload's plaintext
      Returns:
      the length of the message this handshake would produce for a payload with the given length
      Throws:
      IllegalStateException - if this handshake is not currently expecting to send a message to its peer
    • getPayloadLength

      public int getPayloadLength(int handshakeMessageLength)
      Returns the length of the plaintext of a payload contained in a Noise handshake message of the given length and with this handshake's current state.
      Parameters:
      handshakeMessageLength - the length of a Noise handshake message received from this party's peer
      Returns:
      the length of the plaintext of a payload contained in a handshake message of the given length
      Throws:
      IllegalStateException - if this handshake is not currently expecting to receive a message from its peer
      IllegalArgumentException - if the given handshake message length shorter than the minimum expected length of an incoming handshake message
    • writeMessage

      public byte[] writeMessage(@Nullable byte[] payload)

      Writes the next Noise handshake message for this handshake instance, advancing this handshake's internal state. The returned message will include any key material specified by this handshake's current message pattern and either the plaintext or a ciphertext of the given payload.

      Note that the security properties for the optional payload vary by handshake pattern, message, and sender role. Callers are responsible for verifying that the security properties associated with ny handshake message are suitable for their use case.

      Parameters:
      payload - the payload to include in this handshake message; may be null
      Returns:
      a new byte array containing the resulting handshake message
      Throws:
      IllegalArgumentException - if the message produced for the given payload would be larger than the maximum allowed Noise handshake message size
      IllegalStateException - if this handshake is not currently expecting to send a handshake message to its peer
      See Also:
    • writeMessage

      public int writeMessage(@Nullable byte[] payload, int payloadOffset, int payloadLength, byte[] message, int messageOffset) throws ShortBufferException

      Writes the next Noise handshake message for this handshake instance into the given array, advancing this handshake's internal state. The resulting message will include any key material specified by this handshake's current message pattern and either the plaintext or a ciphertext of the given payload.

      Note that the security properties for the optional payload vary by handshake pattern, message, and sender role. Callers are responsible for verifying that the security properties associated with ny handshake message are suitable for their use case.

      Parameters:
      payload - a byte array containing the optional payload for this handshake message; may be null
      payloadOffset - the offset within payload where the payload begins; ignored if payload is null
      payloadLength - the length of the payload within payload
      message - a byte array into which to write the resulting handshake message
      messageOffset - the position within message at which to begin writing the handshake message
      Returns:
      the number of bytes written to message
      Throws:
      IllegalArgumentException - if the message produced for the given payload would be larger than the maximum allowed Noise handshake message size
      IllegalStateException - if this handshake is not currently expecting to send a handshake message to its peer
      ShortBufferException - if message is not large enough (after its offset) to hold the handshake message
      See Also:
    • writeMessage

      public ByteBuffer writeMessage(@Nullable ByteBuffer payload)

      Writes the next Noise handshake message for this handshake instance, advancing this handshake's internal state. The returned message will include any key material specified by this handshake's current message pattern and either the plaintext or a ciphertext of the given payload.

      Note that the security properties for the optional payload vary by handshake pattern, message, and sender role. Callers are responsible for verifying that the security properties associated with ny handshake message are suitable for their use case.

      Parameters:
      payload - the payload to include in this handshake message; may be null
      Returns:
      a new byte buffer containing the resulting handshake message
      Throws:
      IllegalArgumentException - if the message produced for the given payload would be larger than the maximum allowed Noise handshake message size
      IllegalStateException - if this handshake is not currently expecting to send a handshake message to its peer
      See Also:
    • writeMessage

      public int writeMessage(@Nullable ByteBuffer payload, ByteBuffer message) throws ShortBufferException

      Writes the next Noise handshake message for this handshake instance into the given buffer, advancing this handshake's internal state. The resulting message will include any key material specified by this handshake's current message pattern and either the plaintext or a ciphertext of the given payload.

      Note that the security properties for the optional payload vary by handshake pattern, message, and sender role. Callers are responsible for verifying that the security properties associated with ny handshake message are suitable for their use case.

      If provided, all payload.remaining() bytes starting at payload.position() are processed. Upon return, the payload buffer's position will be equal to its limit; its limit will not have changed. The message buffer's position will have advanced by n, where n is the value returned by this method; the message buffer's limit will not have changed.

      Parameters:
      payload - a byte buffer containing the optional payload for this handshake message; may be null
      message - a byte buffer into which to write the resulting handshake message
      Returns:
      the number of bytes written to message
      Throws:
      IllegalArgumentException - if the message produced for the given payload would be larger than the maximum allowed Noise handshake message size
      IllegalStateException - if this handshake is not currently expecting to send a handshake message to its peer
      ShortBufferException - if message does not have enough remaining capacity to hold the handshake message
      See Also:
    • readMessage

      public byte[] readMessage(byte[] message) throws AEADBadTagException
      Reads the next handshake message, advancing this handshake's internal state.
      Parameters:
      message - the handshake message to read
      Returns:
      a byte array containing the plaintext of the payload included in the handshake message; may be empty
      Throws:
      AEADBadTagException - if the AEAD tag for any encrypted component of the given handshake message does not match the calculated value
      IllegalArgumentException - if the given message is too short to contain the expected handshake message or if the given message is larger than the maximum allowed Noise handshake message size
    • readMessage

      public int readMessage(byte[] message, int messageOffset, int messageLength, byte[] payload, int payloadOffset) throws ShortBufferException, AEADBadTagException
      Reads the next handshake message, writing the plaintext of the message's payload into the given array and advancing this handshake's internal state.
      Parameters:
      message - a byte array containing the handshake message to read
      messageOffset - the position within message at which the handshake message begins
      messageLength - the length of the handshake message within message
      payload - a byte array into which to write the plaintext of the payload included in the given handshake message
      payloadOffset - the position within payload at which to begin writing the payload
      Returns:
      a byte array containing the plaintext of the payload included in the handshake message; may be empty
      Throws:
      AEADBadTagException - if the AEAD tag for any encrypted component of the given handshake message does not match the calculated value
      ShortBufferException - if payload is too short (after its offset) to hold the plaintext of the payload included in the given handshake message
      IllegalArgumentException - if the given message is too short to contain the expected handshake message or if the given message is larger than the maximum allowed Noise handshake message size
      See Also:
    • readMessage

      public ByteBuffer readMessage(ByteBuffer message) throws AEADBadTagException
      Reads the next handshake message, advancing this handshake's internal state.
      Parameters:
      message - the handshake message to read
      Returns:
      a new byte buffer containing the plaintext of the payload included in the handshake message; may be empty
      Throws:
      AEADBadTagException - if the AEAD tag for any encrypted component of the given handshake message does not match the calculated value
      IllegalArgumentException - if the given message is too short to contain the expected handshake message or if the given message is larger than the maximum allowed Noise handshake message size
    • readMessage

      public int readMessage(ByteBuffer message, ByteBuffer payload) throws ShortBufferException, AEADBadTagException

      Reads the next handshake message, writing the plaintext of the message's payload into the given buffer and advancing this handshake's internal state.

      All message.remaining() bytes starting at message.position() are processed. Upon return, the message buffer's position will be equal to its limit; its limit will not have changed. The payload buffer's position will have advanced by n, where n is the value returned by this method; the payload buffer's limit will not have changed.

      Parameters:
      message - a byte buffer containing the handshake message to read
      payload - a byte buffer into which to write the plaintext of the payload included in the given handshake message
      Returns:
      the number of bytes written to payload
      Throws:
      AEADBadTagException - if the AEAD tag for any encrypted component of the given handshake message does not match the calculated value
      ShortBufferException - if payload does not have enough remaining capacity to hold the plaintext of the payload included in the given handshake message
      IllegalArgumentException - if the given message is too short to contain the expected handshake message or if the given message is larger than the maximum allowed Noise handshake message size
      See Also:
    • fallbackTo

      public NoiseHandshake fallbackTo(String handshakePatternName) throws NoSuchPatternException
      "Falls back" to the named handshake pattern, transferring any appropriate static/ephemeral keys and an empty collection of pre-shared keys.
      Parameters:
      handshakePatternName - the name of the handshake pattern to which to fall back; must be a pattern with a "fallback" modifier
      Returns:
      a new Noise handshake instance that implements the given fallback handshake pattern
      Throws:
      NoSuchPatternException - if the given fallback pattern name is not a recognized Noise handshake pattern name or cannot be derived from a recognized Noise handshake pattern
      IllegalArgumentException - if the given fallback pattern name is not a fallback pattern
      IllegalStateException - if the given fallback pattern requires key material not available to the current handshake
      See Also:
    • fallbackTo

      public NoiseHandshake fallbackTo(String handshakePatternName, @Nullable List<byte[]> preSharedKeys) throws NoSuchPatternException
      "Falls back" to the named handshake pattern, transferring any appropriate static/ephemeral keys and the given collection of pre-shared keys.
      Parameters:
      handshakePatternName - the name of the handshake pattern to which to fall back; must be a pattern with a "fallback" modifier
      preSharedKeys - the pre-shared keys to use in the fallback handshake; may be null
      Returns:
      a new Noise handshake instance that implements the given fallback handshake pattern
      Throws:
      NoSuchPatternException - if the given fallback pattern name is not a recognized Noise handshake pattern name or cannot be derived from a recognized Noise handshake pattern
      IllegalArgumentException - if the given fallback pattern name is not a fallback pattern
      IllegalStateException - if the given fallback pattern requires key material not available to the current handshake
      See Also:
    • toTransport

      public NoiseTransport toTransport()
      Builds a interactive Noise transport object from this handshake. This method may be called exactly once, only if this is a interactive (i.e. not one-way) handshake, and only when the handshake is done.
      Returns:
      a interactive Noise transport object derived from this completed handshake
      Throws:
      IllegalStateException - if this is a one-way handshake, the handshake has not finished, or this handshake has previously been "split" into a Noise transport object
      See Also:
    • toTransportReader

      public NoiseTransportReader toTransportReader()
      Builds a read-only Noise transport object from this handshake. This method may be called exactly once, only if this is a one-way handshake, only if this is the handshake for the responder, and only when the handshake is done.
      Returns:
      a read-only Noise transport object derived from this completed handshake
      Throws:
      IllegalStateException - if this is not a one-way handshake, if this method is called on the initiator side of a one-way handshake, if the handshake has not finished, or this handshake has previously been "split" into a Noise transport object
      See Also:
    • toTransportWriter

      public NoiseTransportWriter toTransportWriter()
      Builds a write-only Noise transport object from this handshake. This method may be called exactly once, only if this is a one-way handshake, only if this is the handshake for the initiator, and only when the handshake is done.
      Returns:
      a read-only Noise transport object derived from this completed handshake
      Throws:
      IllegalStateException - if this is not a one-way handshake, if this method is called on the responder side of a one-way handshake, if the handshake has not finished, or this handshake has previously been "split" into a Noise transport object
      See Also:
    • getHash

      public byte[] getHash()
      Returns a hash of this handshake's state that uniquely identifies the Noise session. May only be called once the handshake has been transformed into a transport instance.
      Returns:
      a hash of this handshake's state that uniquely identifies the Noise session
      Throws:
      IllegalStateException - if this handshake instance has not yet be transformed into a transport instance
      See Also: