Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance. Project price only 1 $
You can buy this project and download/modify it how often you want.
/*
* Copyright (C) 2016 Southern Storm Software, Pty Ltd.
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*/
package com.southernstorm.noise.protocol;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import javax.crypto.BadPaddingException;
import javax.crypto.ShortBufferException;
import net.i2p.crypto.KeyFactory;
/**
* Interface to a Noise handshake.
*/
public class HandshakeState implements Destroyable, Cloneable {
private final SymmetricState symmetric;
private final boolean isInitiator;
private DHState localKeyPair;
private DHState localEphemeral;
private DHState remotePublicKey;
private DHState remoteEphemeral;
private int action;
private final int requirements;
private int patternIndex;
private boolean wasCloned;
/**
* Enumerated value that indicates that the handshake object
* is handling the initiator role.
*/
public static final int INITIATOR = 1;
/**
* Enumerated value that indicates that the handshake object
* is handling the responder role.
*/
public static final int RESPONDER = 2;
/**
* No action is required of the application yet because the
* handshake has not started.
*/
public static final int NO_ACTION = 0;
/**
* The HandshakeState expects the application to write the
* next message payload for the handshake.
*/
public static final int WRITE_MESSAGE = 1;
/**
* The HandshakeState expects the application to read the
* next message payload from the handshake.
*/
public static final int READ_MESSAGE = 2;
/**
* The handshake has failed due to some kind of error.
*/
public static final int FAILED = 3;
/**
* The handshake is over and the application is expected to call
* split() and begin data session communications.
*/
public static final int SPLIT = 4;
/**
* The handshake is complete and the data session ciphers
* have been split() out successfully.
*/
public static final int COMPLETE = 5;
/** I2P for debugging */
private static final String STATE_NAMES[] = { "NO_ACTION", "WRITE_MESSAGE", "READ_MESSAGE", "FAILED", "SPLIT", "COMPLETE" };
/**
* Local static keypair is required for the handshake.
*/
private static final int LOCAL_REQUIRED = 0x01;
/**
* Remote static keypai is required for the handshake.
*/
private static final int REMOTE_REQUIRED = 0x02;
/**
* Pre-shared key is required for the handshake.
*/
private static final int PSK_REQUIRED = 0x04;
/**
* Ephemeral key for fallback pre-message has been provided.
*/
private static final int FALLBACK_PREMSG = 0x08;
/**
* The local public key is part of the pre-message.
*/
private static final int LOCAL_PREMSG = 0x10;
/**
* The remote public key is part of the pre-message.
*/
private static final int REMOTE_PREMSG = 0x20;
/**
* Fallback is possible from this pattern (two-way, ends in "K").
*/
private static final int FALLBACK_POSSIBLE = 0x40;
/** NTCP2 */
public static final String protocolName = "Noise_XKaesobfse+hs2+hs3_25519_ChaChaPoly_SHA256";
/** Ratchet */
public static final String protocolName2 = "Noise_IKelg2+hs2_25519_ChaChaPoly_SHA256";
/** Tunnels */
public static final String protocolName3 = "Noise_N_25519_ChaChaPoly_SHA256";
/** SSU2 */
public static final String protocolName4 = "Noise_XKchaobfse+hs1+hs2+hs3_25519_ChaChaPoly_SHA256";
private static final String prefix;
private final String patternId;
/** NTCP2 */
public static final String PATTERN_ID_XK = "XK";
/** Ratchet */
public static final String PATTERN_ID_IK = "IK";
/** Tunnels */
public static final String PATTERN_ID_N = "N";
/** same as N but no post-mixHash needed */
public static final String PATTERN_ID_N_NO_RESPONSE = "N!";
/** SSU2 */
public static final String PATTERN_ID_XK_SSU2 = "XK-SSU2";
private static String dh;
private static final String cipher;
private static final String hash;
private final short[] pattern;
private static final short[] PATTERN_XK;
private static final short[] PATTERN_IK;
private static final short[] PATTERN_N;
static {
// Parse the protocol name into its components.
// XK
String[] components = protocolName.split("_");
if (components.length != 5)
throw new IllegalArgumentException("Protocol name must have 5 components");
prefix = components[0];
String id = components[1].substring(0, 2);
if (!PATTERN_ID_XK.equals(id))
throw new IllegalArgumentException();
dh = components[2];
cipher = components[3];
hash = components[4];
if (!prefix.equals("Noise") && !prefix.equals("NoisePSK"))
throw new IllegalArgumentException("Prefix must be Noise or NoisePSK");
PATTERN_XK = Pattern.lookup(id);
if (PATTERN_XK == null)
throw new IllegalArgumentException("Handshake pattern is not recognized");
if (!dh.equals("25519"))
throw new IllegalArgumentException("Unknown Noise DH algorithm name: " + dh);
// IK
components = protocolName2.split("_");
id = components[1].substring(0, 2);
if (!PATTERN_ID_IK.equals(id))
throw new IllegalArgumentException();
PATTERN_IK = Pattern.lookup(id);
if (PATTERN_IK == null)
throw new IllegalArgumentException("Handshake pattern is not recognized");
// N
components = protocolName3.split("_");
id = components[1];
if (!PATTERN_ID_N.equals(id))
throw new IllegalArgumentException();
PATTERN_N = Pattern.lookup(id);
if (PATTERN_N == null)
throw new IllegalArgumentException("Handshake pattern is not recognized");
// XK-SSU2
components = protocolName4.split("_");
id = components[1].substring(0, 2);
if (!PATTERN_ID_XK.equals(id))
throw new IllegalArgumentException();
}
/**
* Creates a new Noise handshake.
* Noise protocol name is hardcoded.
*
* @param patternId XK, IK, or N
* @param role The role, HandshakeState.INITIATOR or HandshakeState.RESPONDER.
* @param xdh The key pair factory for ephemeral keys
*
* @throws IllegalArgumentException The protocolName is not
* formatted correctly, or the role is not recognized.
*
* @throws NoSuchAlgorithmException One of the cryptographic algorithms
* that is specified in the protocolName is not supported.
*/
public HandshakeState(String patternId, int role, KeyFactory xdh) throws NoSuchAlgorithmException
{
this.patternId = patternId;
if (patternId.equals(PATTERN_ID_XK))
pattern = PATTERN_XK;
else if (patternId.equals(PATTERN_ID_IK))
pattern = PATTERN_IK;
else if (patternId.equals(PATTERN_ID_N))
pattern = PATTERN_N;
else if (patternId.equals(PATTERN_ID_N_NO_RESPONSE)) // same as N but no post-mixHash needed
pattern = PATTERN_N;
else if (patternId.equals(PATTERN_ID_XK_SSU2))
pattern = PATTERN_XK;
else
throw new IllegalArgumentException("Handshake pattern is not recognized");
short flags = pattern[0];
int extraReqs = 0;
if ((flags & Pattern.FLAG_REMOTE_REQUIRED) != 0 && patternId.length() > 1)
extraReqs |= FALLBACK_POSSIBLE;
if (role == RESPONDER) {
// Reverse the pattern flags so that the responder is "local".
flags = Pattern.reverseFlags(flags);
}
// Check that the role is correctly specified.
if (role != INITIATOR && role != RESPONDER)
throw new IllegalArgumentException("Role must be initiator or responder");
// Initialize this object. This will also create the cipher and hash objects.
symmetric = new SymmetricState(cipher, hash, patternId);
isInitiator = (role == INITIATOR);
action = NO_ACTION;
requirements = extraReqs | computeRequirements(flags, prefix, role, false);
patternIndex = 1;
// Create the DH objects that we will need later.
if ((flags & Pattern.FLAG_LOCAL_STATIC) != 0)
localKeyPair = new Curve25519DHState(xdh);
if ((flags & Pattern.FLAG_LOCAL_EPHEMERAL) != 0)
localEphemeral = new Curve25519DHState(xdh);
if ((flags & Pattern.FLAG_REMOTE_STATIC) != 0)
remotePublicKey = new Curve25519DHState(xdh);
if ((flags & Pattern.FLAG_REMOTE_EPHEMERAL) != 0)
remoteEphemeral = new Curve25519DHState(xdh);
}
/**
* Copy constructor for cloning
* @since 0.9.44
*/
protected HandshakeState(HandshakeState o) throws CloneNotSupportedException {
// everything is shallow copied except for symmetric state and keys
// so destroy() doesn't zero them out later
symmetric = o.symmetric.clone();
isInitiator = o.isInitiator;
if (o.localKeyPair != null)
localKeyPair = o.localKeyPair.clone();
if (o.localEphemeral != null) {
if (isInitiator) {
// always save Alice's local keys
localEphemeral = o.localEphemeral.clone();
} else {
if (o.wasCloned) {
// new keys after first time for Bob
localEphemeral = o.localEphemeral.clone();
localEphemeral.generateKeyPair();
} else {
// first time for Bob, use the eph. keys previously generated
localEphemeral = o.localEphemeral;
o.wasCloned = true;
}
}
}
if (o.remotePublicKey != null)
remotePublicKey = o.remotePublicKey.clone();
if (o.remoteEphemeral != null)
remoteEphemeral = o.remoteEphemeral.clone();
action = o.action;
if (action == SPLIT || action == COMPLETE)
throw new CloneNotSupportedException("clone after NSR");
requirements = o.requirements;
patternIndex = o.patternIndex;
patternId = o.patternId;
pattern = o.pattern;
}
/**
* Gets the name of the Noise protocol.
*
* @return The protocol name.
*/
public String getProtocolName()
{
return symmetric.getProtocolName();
}
/**
* Gets the role for this handshake.
*
* @return The role, HandshakeState.INITIATOR or HandshakeState.RESPONDER.
*/
public int getRole()
{
return isInitiator ? INITIATOR : RESPONDER;
}
/**
* Gets the keypair object for the local static key.
*
* @return The keypair, or null if a local static key is not required.
*/
public DHState getLocalKeyPair()
{
return localKeyPair;
}
/**
* Gets the keypair object for the local ephemeral key.
*
* I2P
*
* @return The keypair, or null if a local ephemeral key is not required or has not been generated.
* @since 0.9.44
*/
public DHState getLocalEphemeralKeyPair()
{
return localEphemeral;
}
/**
* Determine if this handshake requires a local static key.
*
* @return true if a local static key is needed; false if not.
*
* If the local static key has already been set, then this function
* will return false.
*/
public boolean needsLocalKeyPair()
{
if (localKeyPair != null)
return !localKeyPair.hasPrivateKey();
else
return false;
}
/**
* Determine if this handshake has already been configured
* with a local static key.
*
* @return true if the local static key has been configured;
* false if not.
*/
public boolean hasLocalKeyPair()
{
if (localKeyPair != null)
return localKeyPair.hasPrivateKey();
else
return false;
}
/**
* Gets the public key object for the remote static key.
*
* @return The public key, or null if a remote static key
* is not required.
*/
public DHState getRemotePublicKey()
{
return remotePublicKey;
}
/**
* Determine if this handshake requires a remote static key.
*
* @return true if a remote static key is needed; false if not.
*
* If the remote static key has already been set, then this function
* will return false.
*/
public boolean needsRemotePublicKey()
{
if (remotePublicKey != null)
return !remotePublicKey.hasPublicKey();
else
return false;
}
/**
* Determine if this handshake has already been configured
* with a remote static key.
*
* @return true if the remote static key has been configured;
* false if not.
*/
public boolean hasRemotePublicKey()
{
if (remotePublicKey != null)
return remotePublicKey.hasPublicKey();
else
return false;
}
// Empty value for when the prologue is not supplied.
private static final byte[] emptyPrologue = new byte [0];
/**
* Starts the handshake running.
*
* This function is called after all of the handshake parameters have been
* provided to the HandshakeState object. This function should be followed
* by calls to writeMessage() or readMessage() to process the handshake
* messages. The getAction() function indicates the action to take next.
*
* @throws IllegalStateException The handshake has already started, or one or
* more of the required parameters has not been supplied.
*
* @throws UnsupportedOperationException An attempt was made to start a
* fallback handshake pattern without first calling fallback() on a
* previous handshake.
*
* @see #getAction()
* @see #writeMessage(byte[], int, byte[], int, int)
* @see #readMessage(byte[], int, int, byte[], int)
* see #fallback()
*/
public void start()
{
if (action != NO_ACTION) {
throw new IllegalStateException
("Handshake has already started; cannot start again");
}
if ((pattern[0] & Pattern.FLAG_REMOTE_EPHEM_REQ) != 0 &&
(requirements & FALLBACK_PREMSG) == 0) {
throw new UnsupportedOperationException
("Cannot start a fallback pattern");
}
// Check that we have satisfied all of the pattern requirements.
if ((requirements & LOCAL_REQUIRED) != 0) {
if (localKeyPair == null || !localKeyPair.hasPrivateKey())
throw new IllegalStateException("Local static key required");
}
if ((requirements & REMOTE_REQUIRED) != 0) {
if (remotePublicKey == null || !remotePublicKey.hasPublicKey())
throw new IllegalStateException("Remote static key required");
}
// Hash the prologue value.
// Now precalculated in SymmetricState.
//symmetric.mixHash(emptyPrologue, 0, 0);
// Mix the pre-supplied public keys into the handshake hash.
if (isInitiator) {
if ((requirements & LOCAL_PREMSG) != 0)
symmetric.mixPublicKey(localKeyPair);
if ((requirements & FALLBACK_PREMSG) != 0) {
symmetric.mixPublicKey(remoteEphemeral);
}
if ((requirements & REMOTE_PREMSG) != 0)
symmetric.mixPublicKey(remotePublicKey);
} else {
if ((requirements & REMOTE_PREMSG) != 0)
symmetric.mixPublicKey(remotePublicKey);
if ((requirements & FALLBACK_PREMSG) != 0) {
symmetric.mixPublicKey(localEphemeral);
}
if ((requirements & LOCAL_PREMSG) != 0)
symmetric.mixPublicKey(localKeyPair);
}
// The handshake has officially started - set the first action.
if (isInitiator)
action = WRITE_MESSAGE;
else
action = READ_MESSAGE;
}
/**
* Gets the next action that the application should perform for
* the handshake part of the protocol.
*
* @return One of HandshakeState.NO_ACTION, HandshakeState.WRITE_MESSAGE,
* HandshakeState.READ_MESSAGE, HandshakeState.SPLIT, or
* HandshakeState.FAILED.
*/
public int getAction()
{
return action;
}
/**
* Mixes the result of a Diffie-Hellman calculation into the chaining key.
*
* @param local Local private key object.
* @param remote Remote public key object.
*/
private void mixDH(DHState local, DHState remote)
{
if (local == null || remote == null)
throw new IllegalStateException("Pattern definition error");
int len = local.getSharedKeyLength();
byte[] shared = new byte [len];
try {
local.calculate(shared, 0, remote);
symmetric.mixKey(shared, 0, len);
} finally {
Noise.destroy(shared);
}
}
/**
* Writes a message payload during the handshake.
*
* Payload (plaintext) and message (encrypted) may be in the same buffer if the payload
* if offset enough past the message offset to leave room for the
* key(s) and/or MAC. For 32 byte keys and 16 byte MACs,
* if message == payload, payloadOffset must be at least this much
* greater than messageOffset:
*
* XK: Message 1: 32; message 2: 32; message 3: 48
*
* IK: Message 1: 80; message 2: 48
*
* N: Message 1: 32
*
* @param message The buffer that will be populated with the
* handshake packet to be written to the transport.
* @param messageOffset First offset within the message buffer
* to be populated.
* @param payload Buffer containing the payload to add to the
* handshake message; can be null if there is no payload.
* @param payloadOffset Offset into the payload buffer of the
* first payload buffer.
* @param payloadLength Length of the payload in bytes.
*
* @return The length of the data written to the message buffer.
*
* @throws IllegalStateException The action is not WRITE_MESSAGE.
*
* @throws IllegalArgumentException The payload is null, but
* payloadOffset or payloadLength is non-zero.
*
* @throws ShortBufferException The message buffer does not have
* enough space for the handshake message.
*
* @see #getAction()
* @see #readMessage(byte[], int, int, byte[], int)
*/
public int writeMessage(byte[] message, int messageOffset, byte[] payload, int payloadOffset, int payloadLength) throws ShortBufferException
{
int messagePosn = messageOffset;
boolean success = false;
// Validate the parameters and state.
if (action != WRITE_MESSAGE) {
throw new IllegalStateException
("Handshake state " + STATE_NAMES[action] + " does not allow writing messages");
}
if (payload == null && (payloadOffset != 0 || payloadLength != 0)) {
throw new IllegalArgumentException("Invalid payload argument");
}
if (messageOffset > message.length) {
throw new ShortBufferException();
}
// Format the message.
try {
// Process tokens until the direction changes or the patten ends.
loop:
for (;;) {
if (patternIndex >= pattern.length) {
// The pattern has finished, so the next action is "split".
action = SPLIT;
break;
}
short token = pattern[patternIndex++];
if (token == Pattern.FLIP_DIR) {
// Change directions, so this message is complete and the
// next action is "read message".
action = READ_MESSAGE;
break;
}
int space = message.length - messagePosn;
int len, macLen;
switch (token) {
case Pattern.E:
{
// Generate a local ephemeral keypair and add the public
// key to the message. If we are running fixed vector tests,
// then the ephemeral key may have already been provided.
if (localEphemeral == null)
throw new IllegalStateException("Pattern definition error");
localEphemeral.generateKeyPair();
len = localEphemeral.getPublicKeyLength();
if (space < len)
throw new ShortBufferException();
localEphemeral.getPublicKey(message, messagePosn);
symmetric.mixHash(message, messagePosn, len);
// If the protocol is using pre-shared keys, then also mix
// the local ephemeral key into the chaining key.
messagePosn += len;
}
break;
case Pattern.S:
{
// Encrypt the local static public key and add it to the message.
if (localKeyPair == null)
throw new IllegalStateException("Pattern definition error");
len = localKeyPair.getPublicKeyLength();
macLen = symmetric.getMACLength();
if (space < (len + macLen))
throw new ShortBufferException();
localKeyPair.getPublicKey(message, messagePosn);
messagePosn += symmetric.encryptAndHash(message, messagePosn, message, messagePosn, len);
}
break;
case Pattern.EE:
{
// DH operation with initiator and responder ephemeral keys.
mixDH(localEphemeral, remoteEphemeral);
}
break;
case Pattern.ES:
{
// DH operation with initiator ephemeral and responder static keys.
if (isInitiator)
mixDH(localEphemeral, remotePublicKey);
else
mixDH(localKeyPair, remoteEphemeral);
}
break;
case Pattern.SE:
{
// DH operation with initiator static and responder ephemeral keys.
if (isInitiator)
mixDH(localKeyPair, remoteEphemeral);
else
mixDH(localEphemeral, remotePublicKey);
}
break;
case Pattern.SS:
{
// I2P N extension to IK
if (patternId.equals(PATTERN_ID_IK) &&
localKeyPair.isNullPublicKey()) {
break loop;
}
// DH operation with initiator and responder static keys.
mixDH(localKeyPair, remotePublicKey);
}
break;
default:
{
// Unknown token code. Abort.
throw new IllegalStateException("Unknown handshake token " + Integer.toString(token));
}
}
}
// Add the payload to the message buffer and encrypt it.
if (payload != null) {
// no need to hash for N, we don't split() and no more messages follow
if (patternId.equals(PATTERN_ID_N_NO_RESPONSE))
messagePosn += symmetric.encryptOnly(payload, payloadOffset, message, messagePosn, payloadLength);
else
messagePosn += symmetric.encryptAndHash(payload, payloadOffset, message, messagePosn, payloadLength);
} else {
// no need to hash for N, we don't split() and no more messages follow
if (patternId.equals(PATTERN_ID_N_NO_RESPONSE))
messagePosn += symmetric.encryptOnly(message, messagePosn, message, messagePosn, 0);
else
messagePosn += symmetric.encryptAndHash(message, messagePosn, message, messagePosn, 0);
}
success = true;
} finally {
// If we failed, then clear any sensitive data that may have
// already been written to the message buffer.
if (!success) {
Arrays.fill(message, messageOffset, message.length - messageOffset, (byte)0);
action = FAILED;
}
}
return messagePosn - messageOffset;
}
/**
* Reads a message payload during the handshake.
*
* Payload (plaintext) and message (encrypted) may be in the same buffer
* and have the same offset.
*
* @param message Buffer containing the incoming handshake
* that was read from the transport.
* @param messageOffset Offset of the first message byte.
* @param messageLength The length of the incoming message.
* @param payload Buffer that will be populated with the message payload.
* @param payloadOffset Offset of the first byte in the
* payload buffer to be populated with payload data.
*
* @return The length of the payload.
*
* @throws IllegalStateException The action is not READ_MESSAGE.
*
* @throws ShortBufferException The message buffer does not have
* sufficient bytes for a valid message or the payload buffer does
* not have enough space for the decrypted payload.
*
* @throws BadPaddingException A MAC value in the message failed
* to verify.
*
* @see #getAction()
* @see #writeMessage(byte[], int, byte[], int, int)
*/
public int readMessage(byte[] message, int messageOffset, int messageLength, byte[] payload, int payloadOffset) throws ShortBufferException, BadPaddingException
{
boolean success = false;
int messageEnd = messageOffset + messageLength;
// Validate the parameters.
if (action != READ_MESSAGE) {
throw new IllegalStateException
("Handshake state " + STATE_NAMES[action] + " does not allow reading messages");
}
if (messageOffset > message.length || payloadOffset > payload.length) {
throw new ShortBufferException();
}
if (messageLength > (message.length - messageOffset)) {
throw new ShortBufferException();
}
// Process the message.
try {
// Process tokens until the direction changes or the patten ends.
loop:
for (;;) {
if (patternIndex >= pattern.length) {
// The pattern has finished, so the next action is "split".
action = SPLIT;
break;
}
short token = pattern[patternIndex++];
if (token == Pattern.FLIP_DIR) {
// Change directions, so this message is complete and the
// next action is "write message".
action = WRITE_MESSAGE;
break;
}
int space = messageEnd - messageOffset;
int len, macLen;
switch (token) {
case Pattern.E:
{
// Save the remote ephemeral key and hash it.
if (remoteEphemeral == null)
throw new IllegalStateException("Pattern definition error");
len = remoteEphemeral.getPublicKeyLength();
if (space < len)
throw new ShortBufferException();
symmetric.mixHash(message, messageOffset, len);
remoteEphemeral.setPublicKey(message, messageOffset);
if (remoteEphemeral.isNullPublicKey()) {
// The remote ephemeral key is null, which means that it is
// not contributing anything to the security of the session
// and is in fact downgrading the security to "none at all"
// in some of the message patterns. Reject all such keys.
throw new BadPaddingException("Null remote public key");
}
// If the protocol is using pre-shared keys, then also mix
// the remote ephemeral key into the chaining key.
messageOffset += len;
}
break;
case Pattern.S:
{
// Decrypt and read the remote static key.
if (remotePublicKey == null)
throw new IllegalStateException("Pattern definition error");
len = remotePublicKey.getPublicKeyLength();
macLen = symmetric.getMACLength();
if (space < (len + macLen))
throw new ShortBufferException();
byte[] temp = new byte [len];
try {
if (symmetric.decryptAndHash(message, messageOffset, temp, 0, len + macLen) != len)
throw new ShortBufferException();
remotePublicKey.setPublicKey(temp, 0);
} finally {
Noise.destroy(temp);
}
messageOffset += len + macLen;
}
break;
case Pattern.EE:
{
// DH operation with initiator and responder ephemeral keys.
mixDH(localEphemeral, remoteEphemeral);
}
break;
case Pattern.ES:
{
// DH operation with initiator ephemeral and responder static keys.
if (isInitiator)
mixDH(localEphemeral, remotePublicKey);
else
mixDH(localKeyPair, remoteEphemeral);
}
break;
case Pattern.SE:
{
// DH operation with initiator static and responder ephemeral keys.
if (isInitiator)
mixDH(localKeyPair, remoteEphemeral);
else
mixDH(localEphemeral, remotePublicKey);
}
break;
case Pattern.SS:
{
// I2P N extension to IK
if (patternId.equals(PATTERN_ID_IK) &&
remotePublicKey.isNullPublicKey()) {
break loop;
}
// DH operation with initiator and responder static keys.
mixDH(localKeyPair, remotePublicKey);
}
break;
default:
{
// Unknown token code. Abort.
throw new IllegalStateException("Unknown handshake token " + Integer.toString(token));
}
}
}
// Decrypt the message payload.
int payloadLength;
// no need to hash for N, we don't split() and no more messages follow
if (patternId.equals(PATTERN_ID_N_NO_RESPONSE))
payloadLength = symmetric.decryptOnly(message, messageOffset, payload, payloadOffset, messageEnd - messageOffset);
else
payloadLength = symmetric.decryptAndHash(message, messageOffset, payload, payloadOffset, messageEnd - messageOffset);
success = true;
return payloadLength;
} finally {
// If we failed, then clear any sensitive data that may have
// already been written to the payload buffer.
if (!success) {
Arrays.fill(payload, payloadOffset, payload.length - payloadOffset, (byte)0);
action = FAILED;
}
}
}
/**
* Splits the transport encryption CipherState objects out of
* this HandshakeState object once the handshake completes.
*
* @return The pair of ciphers for sending and receiving.
*
* @throws IllegalStateException The action is not SPLIT.
*/
public CipherStatePair split()
{
if (action != SPLIT) {
throw new IllegalStateException
("Handshake has not finished, state: " + STATE_NAMES[action]);
}
CipherStatePair pair = symmetric.split();
if (!isInitiator)
pair.swap();
action = COMPLETE;
return pair;
}
/**
* Splits the transport encryption CipherState objects out of
* this HandshakeObject after mixing in a secondary symmetric key.
*
* @param secondaryKey The buffer containing the secondary key.
* @param offset The offset of the first secondary key byte.
* @param length The length of the secondary key in bytes, which
* must be either 0 or 32.
* @return The pair of ciphers for sending and receiving.
*
* @throws IllegalStateException The action is not SPLIT.
*
* @throws IllegalArgumentException The length is not 0 or 32.
*/
public CipherStatePair split(byte[] secondaryKey, int offset, int length)
{
if (action != SPLIT) {
throw new IllegalStateException
("Handshake has not finished, state: " + STATE_NAMES[action]);
}
CipherStatePair pair = symmetric.split(secondaryKey, offset, length);
if (!isInitiator) {
// Swap the sender and receiver objects for the responder
// to make it easier on the application to know which is which.
pair.swap();
}
action = COMPLETE;
return pair;
}
/**
* Gets the current value of the handshake hash.
*
* @return The handshake hash. This must not be modified by the caller.
*
* @throws IllegalStateException The action is not SPLIT or COMPLETE.
*/
public byte[] getHandshakeHash()
{
if (action != SPLIT && action != COMPLETE) {
throw new IllegalStateException
("Handshake has not finished, state: " + STATE_NAMES[action]);
}
return symmetric.getHandshakeHash();
}
@Override
public void destroy() {
if (symmetric != null)
symmetric.destroy();
if (localKeyPair != null)
localKeyPair.destroy();
if (localEphemeral != null)
localEphemeral.destroy();
if (remotePublicKey != null)
remotePublicKey.destroy();
if (remoteEphemeral != null)
remoteEphemeral.destroy();
}
/**
* Computes the requirements for a handshake.
*
* @param flags The flags from the handshake's pattern.
* @param prefix The prefix from the protocol name; typically
* "Noise" or "NoisePSK".
* @param role The role, HandshakeState.INITIATOR or HandshakeState.RESPONDER.
* @param isFallback Set to true if we need the requirements for a
* fallback pattern; false for a regular pattern.
*
* @return The set of requirements for the handshake.
*/
private static int computeRequirements(short flags, String prefix, int role, boolean isFallback)
{
int requirements = 0;
if ((flags & Pattern.FLAG_LOCAL_STATIC) != 0) {
requirements |= LOCAL_REQUIRED;
}
if ((flags & Pattern.FLAG_LOCAL_REQUIRED) != 0) {
requirements |= LOCAL_REQUIRED;
requirements |= LOCAL_PREMSG;
}
if ((flags & Pattern.FLAG_REMOTE_REQUIRED) != 0) {
requirements |= REMOTE_REQUIRED;
requirements |= REMOTE_PREMSG;
}
if ((flags & (Pattern.FLAG_REMOTE_EPHEM_REQ |
Pattern.FLAG_LOCAL_EPHEM_REQ)) != 0) {
if (isFallback)
requirements |= FALLBACK_PREMSG;
}
return requirements;
}
/**
* I2P for mixing in padding in messages 1 and 2
*/
public void mixHash(byte[] data, int offset, int length) {
symmetric.mixHash(data, offset, length);
}
/**
* I2P for getting chaining key for siphash calc
* @return a copy
*/
public byte[] getChainingKey() {
return symmetric.getChainingKey();
}
/**
* I2P
* Must be called before both eph. keys set.
* @since 0.9.44
*/
@Override
public synchronized HandshakeState clone() throws CloneNotSupportedException {
return new HandshakeState(this);
}
/**
* I2P debug
*/
@Override
public String toString() {
StringBuilder buf = new StringBuilder(256);
buf.append(patternId);
buf.append(' ').append(STATE_NAMES[action]);
buf.append(" Handshake State:\n");
buf.append(symmetric.toString());
byte[] tmp = new byte[32];
DHState dh = localKeyPair;
buf.append("Local static public key (s) : ");
if (dh != null && dh.hasPublicKey()) {
dh.getPublicKey(tmp, 0);
buf.append(net.i2p.data.Base64.encode(tmp));
} else {
buf.append("null");
}
buf.append('\n');
dh = remotePublicKey;
buf.append("Remote static public key (rs) : ");
if (dh != null && dh.hasPublicKey()) {
dh.getPublicKey(tmp, 0);
buf.append(net.i2p.data.Base64.encode(tmp));
} else {
buf.append("null");
}
buf.append('\n');
dh = localEphemeral;
buf.append("Local ephemeral public key (e) : ");
if (dh != null && dh.hasPublicKey()) {
dh.getPublicKey(tmp, 0);
buf.append(net.i2p.data.Base64.encode(tmp));
if (dh.hasEncodedPublicKey()) {
buf.append('\n');
buf.append("Local eph. pub key ELG2 encoded: ");
dh.getEncodedPublicKey(tmp, 0);
buf.append(net.i2p.data.Base64.encode(tmp));
}
} else {
buf.append("null");
}
buf.append('\n');
dh = remoteEphemeral;
buf.append("Remote ephemeral public key (re) : ");
if (dh != null && dh.hasPublicKey()) {
dh.getPublicKey(tmp, 0);
buf.append(net.i2p.data.Base64.encode(tmp));
} else {
buf.append("null");
}
buf.append('\n');
return buf.toString();
}
}