Class NoiseHandshake
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 ClassesModifier and TypeClassDescriptionstatic enum
An enumeration of roles within a Noise handshake. -
Method Summary
Modifier and TypeMethodDescriptionfallbackTo
(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[]
getHash()
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
isDone()
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.readMessage
(ByteBuffer message) 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.writeMessage
(ByteBuffer payload) 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.
-
Method Details
-
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 orfalse
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 orfalse
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 orfalse
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 orfalse
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 peerIllegalArgumentException
- 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 benull
- 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 sizeIllegalStateException
- 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 benull
payloadOffset
- the offset withinpayload
where the payload begins; ignored ifpayload
isnull
payloadLength
- the length of the payload withinpayload
message
- a byte array into which to write the resulting handshake messagemessageOffset
- the position withinmessage
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 sizeIllegalStateException
- if this handshake is not currently expecting to send a handshake message to its peerShortBufferException
- ifmessage
is not large enough (after its offset) to hold the handshake message- See Also:
-
writeMessage
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 benull
- 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 sizeIllegalStateException
- 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 atpayload.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 benull
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 sizeIllegalStateException
- if this handshake is not currently expecting to send a handshake message to its peerShortBufferException
- ifmessage
does not have enough remaining capacity to hold the handshake message- See Also:
-
readMessage
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 valueIllegalArgumentException
- 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 readmessageOffset
- the position withinmessage
at which the handshake message beginsmessageLength
- the length of the handshake message withinmessage
payload
- a byte array into which to write the plaintext of the payload included in the given handshake messagepayloadOffset
- the position withinpayload
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 valueShortBufferException
- ifpayload
is too short (after its offset) to hold the plaintext of the payload included in the given handshake messageIllegalArgumentException
- 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
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 valueIllegalArgumentException
- 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 atmessage.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 readpayload
- 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 valueShortBufferException
- ifpayload
does not have enough remaining capacity to hold the plaintext of the payload included in the given handshake messageIllegalArgumentException
- 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
"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 patternIllegalArgumentException
- if the given fallback pattern name is not a fallback patternIllegalStateException
- 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" modifierpreSharedKeys
- the pre-shared keys to use in the fallback handshake; may benull
- 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 patternIllegalArgumentException
- if the given fallback pattern name is not a fallback patternIllegalStateException
- if the given fallback pattern requires key material not available to the current handshake- See Also:
-
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
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
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:
-