Crypto: Noise modifications to support ratchet:

- Add IK support
- Add generic key factory support
- Add method to get ephemeral key
- Add method to get encoded ephemeral key
- Add clone() support
- Add back ChaCha debug support
This commit is contained in:
zzz
2019-10-23 13:02:00 +00:00
parent 236354e5a8
commit 2c2f90089b
11 changed files with 297 additions and 30 deletions

View File

@ -29,7 +29,7 @@ import com.southernstorm.noise.protocol.Destroyable;
/**
* Simple implementation of the Poly1305 message authenticator.
*/
public final class Poly1305 implements Destroyable {
public final class Poly1305 implements Destroyable, Cloneable {
// The 130-bit intermediate values are broken up into five 26-bit words.
private final byte[] nonce;
@ -324,4 +324,13 @@ public final class Poly1305 implements Destroyable {
Arrays.fill(c, 0);
Arrays.fill(t, (long)0);
}
/**
* I2P
* @since 0.9.44
*/
@Override
public Poly1305 clone() throws CloneNotSupportedException {
return (Poly1305) super.clone();
}
}

View File

@ -41,6 +41,10 @@ public class ChaChaPolyCipherState implements CipherState {
private final byte[] polyKey;
private long n;
private boolean haskey;
// Debug only
private byte[] initialKey;
private static final boolean DEBUG = false;
/**
* Constructs a new cipher state for the "ChaChaPoly" algorithm.
@ -55,6 +59,20 @@ public class ChaChaPolyCipherState implements CipherState {
haskey = false;
}
/**
* Copy constructor for cloning
* @since 0.9.44
*/
protected ChaChaPolyCipherState(ChaChaPolyCipherState o) throws CloneNotSupportedException {
poly = o.poly.clone();
input = Arrays.copyOf(o.input, o.input.length);
output = Arrays.copyOf(o.output, o.output.length);
polyKey = Arrays.copyOf(o.polyKey, o.polyKey.length);
n = o.n;
haskey = o.haskey;
initialKey = o.initialKey;
}
@Override
public void destroy() {
poly.destroy();
@ -80,6 +98,10 @@ public class ChaChaPolyCipherState implements CipherState {
@Override
public void initializeKey(byte[] key, int offset) {
if (DEBUG) {
initialKey = new byte[32];
System.arraycopy(key, 0, initialKey, 0, 32);
}
ChaChaCore.initKey256(input, key, offset);
n = 0;
haskey = true;
@ -249,6 +271,15 @@ public class ChaChaPolyCipherState implements CipherState {
n = nonce;
}
/**
* I2P
* @since 0.9.44
*/
@Override
public ChaChaPolyCipherState clone() throws CloneNotSupportedException {
return new ChaChaPolyCipherState(this);
}
/**
* I2P debug
*/
@ -258,6 +289,15 @@ public class ChaChaPolyCipherState implements CipherState {
buf.append(" Cipher State:\n" +
" nonce: ");
buf.append(n);
// I2P debug
if (DEBUG) {
buf.append("\n" +
" init key: ");
if (haskey)
buf.append(net.i2p.data.Base64.encode(initialKey));
else
buf.append("null");
}
buf.append("\n poly key: ");
if (haskey)
buf.append(net.i2p.data.Base64.encode(polyKey));

View File

@ -33,7 +33,7 @@ import javax.crypto.ShortBufferException;
* will create two CipherState objects for encrypting packets sent to
* the other party, and decrypting packets received from the other party.
*/
public interface CipherState extends Destroyable {
public interface CipherState extends Destroyable, Cloneable {
/**
* Gets the Noise protocol name for this cipher.
@ -155,4 +155,10 @@ public interface CipherState extends Destroyable {
* value goes backwards then security may be compromised.
*/
void setNonce(long nonce);
/**
* I2P
* @since 0.9.44
*/
public CipherState clone() throws CloneNotSupportedException;
}

View File

@ -26,23 +26,25 @@ import java.util.Arrays;
import com.southernstorm.noise.crypto.x25519.Curve25519;
import net.i2p.crypto.KeyFactory;
import net.i2p.crypto.KeyPair;
import net.i2p.router.transport.crypto.X25519KeyFactory;
import net.i2p.router.crypto.ratchet.Elg2KeyPair;
/**
* Implementation of the Curve25519 algorithm for the Noise protocol.
*/
class Curve25519DHState implements DHState {
class Curve25519DHState implements DHState, Cloneable {
private final byte[] publicKey;
private final byte[] privateKey;
private int mode;
private final X25519KeyFactory _xdh;
private final KeyFactory _xdh;
private byte[] encodedPublicKey;
/**
* Constructs a new Diffie-Hellman object for Curve25519.
*/
public Curve25519DHState(X25519KeyFactory xdh)
public Curve25519DHState(KeyFactory xdh)
{
publicKey = new byte [32];
privateKey = new byte [32];
@ -80,6 +82,11 @@ class Curve25519DHState implements DHState {
KeyPair kp = _xdh.getKeys();
System.arraycopy(kp.getPrivate().getData(), 0, privateKey, 0, 32);
System.arraycopy(kp.getPublic().getData(), 0, publicKey, 0, 32);
if (kp instanceof Elg2KeyPair) {
Elg2KeyPair ekp = (Elg2KeyPair) kp;
encodedPublicKey = new byte[32];
System.arraycopy(ekp.getEncoded(), 0, encodedPublicKey, 0, 32);
}
mode = 0x03;
}
@ -111,6 +118,10 @@ class Curve25519DHState implements DHState {
public void setToNullPublicKey() {
Arrays.fill(publicKey, (byte)0);
Arrays.fill(privateKey, (byte)0);
if (encodedPublicKey != null) {
Arrays.fill(encodedPublicKey, (byte)0);
encodedPublicKey = null;
}
mode = 0x01;
}
@ -118,6 +129,10 @@ class Curve25519DHState implements DHState {
public void clearKey() {
Noise.destroy(publicKey);
Noise.destroy(privateKey);
if (encodedPublicKey != null) {
Noise.destroy(encodedPublicKey);
encodedPublicKey = null;
}
mode = 0;
}
@ -141,6 +156,26 @@ class Curve25519DHState implements DHState {
return temp == 0;
}
/**
* I2P
* @since 0.9.44
*/
@Override
public boolean hasEncodedPublicKey() {
return encodedPublicKey != null;
}
/**
* I2P
* @since 0.9.44
*/
@Override
public void getEncodedPublicKey(byte[] key, int offset) {
if (encodedPublicKey == null)
throw new IllegalStateException();
System.arraycopy(encodedPublicKey, 0, key, offset, 32);
}
@Override
public void calculate(byte[] sharedKey, int offset, DHState publicDH) {
if (!(publicDH instanceof Curve25519DHState))
@ -159,4 +194,13 @@ class Curve25519DHState implements DHState {
System.arraycopy(dh.publicKey, 0, publicKey, 0, 32);
mode = dh.mode;
}
/**
* I2P
* @since 0.9.44
*/
@Override
public Curve25519DHState clone() throws CloneNotSupportedException {
return (Curve25519DHState) super.clone();
}
}

View File

@ -25,7 +25,7 @@ package com.southernstorm.noise.protocol;
/**
* Interface to a Diffie-Hellman algorithm for the Noise protocol.
*/
public interface DHState extends Destroyable {
public interface DHState extends Destroyable, Cloneable {
/**
* Gets the Noise protocol name for this Diffie-Hellman algorithm.
@ -132,6 +132,26 @@ public interface DHState extends Destroyable {
*/
boolean isNullPublicKey();
/**
* Determine if this object contains an optional encoded public key.
*
* @return Returns true if this object contains an encoded public key,
* or false if the public key has not yet been set.
*
* @since 0.9.44
*/
boolean hasEncodedPublicKey();
/**
* Gets the public key associated with this object.
*
* @param key The buffer to copy the public key to.
* @param offset The first offset in the key buffer to copy to.
*
* @since 0.9.44
*/
void getEncodedPublicKey(byte[] key, int offset);
/**
* Performs a Diffie-Hellman calculation with this object as the private key.
*
@ -153,4 +173,10 @@ public interface DHState extends Destroyable {
* the same type as this object.
*/
void copyFrom(DHState other);
/**
* I2P
* @since 0.9.44
*/
public DHState clone() throws CloneNotSupportedException;
}

View File

@ -28,12 +28,12 @@ import java.util.Arrays;
import javax.crypto.BadPaddingException;
import javax.crypto.ShortBufferException;
import net.i2p.router.transport.crypto.X25519KeyFactory;
import net.i2p.crypto.KeyFactory;
/**
* Interface to a Noise handshake.
*/
public class HandshakeState implements Destroyable {
public class HandshakeState implements Destroyable, Cloneable {
private final SymmetricState symmetric;
private final boolean isInitiator;
@ -128,36 +128,53 @@ public class HandshakeState implements Destroyable {
private static final int FALLBACK_POSSIBLE = 0x40;
public static final String protocolName = "Noise_XKaesobfse+hs2+hs3_25519_ChaChaPoly_SHA256";
public static final String protocolName2 = "Noise_IKelg2+hs2_25519_ChaChaPoly_SHA256";
private static final String prefix;
private static final String patternId;
private final String patternId;
public static final String PATTERN_ID_XK = "XK";
public static final String PATTERN_ID_IK = "IK";
private static String dh;
private static final String cipher;
private static final String hash;
private static final short[] pattern;
private final short[] pattern;
private static final short[] PATTERN_XK;
private static final short[] PATTERN_IK;
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];
patternId = components[1].substring(0, 2);
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 = Pattern.lookup(patternId);
if (pattern == null)
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");
}
/**
* Creates a new Noise handshake.
* Noise protocol name is hardcoded.
*
* @param patternId XK or IK
* @param role The role, HandshakeState.INITIATOR or HandshakeState.RESPONDER.
* @param xdh The key pair factory for ephemeral keys
*
@ -167,8 +184,15 @@ public class HandshakeState implements Destroyable {
* @throws NoSuchAlgorithmException One of the cryptographic algorithms
* that is specified in the protocolName is not supported.
*/
public HandshakeState(int role, X25519KeyFactory xdh) throws NoSuchAlgorithmException
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
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)
@ -183,7 +207,7 @@ public class HandshakeState implements Destroyable {
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);
symmetric = new SymmetricState(cipher, hash, patternId);
isInitiator = (role == INITIATOR);
action = NO_ACTION;
requirements = extraReqs | computeRequirements(flags, prefix, role, false);
@ -201,6 +225,27 @@ public class HandshakeState implements Destroyable {
}
/**
* Copy constructor for cloning
* @since 0.9.44
*/
protected HandshakeState(HandshakeState o) throws CloneNotSupportedException {
// everything is shallow copied except for symmetric state
symmetric = o.symmetric.clone();
isInitiator = o.isInitiator;
localKeyPair = o.localKeyPair;
localEphemeral = o.localEphemeral;
remotePublicKey = o.remotePublicKey;
remoteEphemeral = o.remoteEphemeral;
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.
*
@ -231,6 +276,19 @@ public class HandshakeState implements Destroyable {
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.
*
@ -534,6 +592,13 @@ public class HandshakeState implements Destroyable {
}
break;
case Pattern.SS:
{
// DH operation with initiator and responder static keys.
mixDH(localKeyPair, remotePublicKey);
}
break;
default:
{
// Unknown token code. Abort.
@ -692,6 +757,13 @@ public class HandshakeState implements Destroyable {
}
break;
case Pattern.SS:
{
// DH operation with initiator and responder static keys.
mixDH(localKeyPair, remotePublicKey);
}
break;
default:
{
// Unknown token code. Abort.
@ -844,13 +916,25 @@ public class HandshakeState implements Destroyable {
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();
buf.append("Handshake State:\n");
StringBuilder buf = new StringBuilder(256);
buf.append(patternId);
buf.append(" Handshake State:\n");
buf.append(symmetric.toString());
byte[] tmp = new byte[32];

View File

@ -71,6 +71,23 @@ class Pattern {
SE
};
private static final short[] noise_pattern_IK = {
FLAG_LOCAL_STATIC |
FLAG_LOCAL_EPHEMERAL |
FLAG_REMOTE_STATIC |
FLAG_REMOTE_EPHEMERAL |
FLAG_REMOTE_REQUIRED,
E,
ES,
S,
SS,
FLIP_DIR,
E,
EE,
SE
};
/**
* Look up the description information for a pattern.
*
@ -81,6 +98,8 @@ class Pattern {
{
if (name.equals("XK"))
return noise_pattern_XK;
else if (name.equals("IK"))
return noise_pattern_IK;
return null;
}

View File

@ -34,32 +34,42 @@ import javax.crypto.ShortBufferException;
/**
* Symmetric state for helping manage a Noise handshake.
*/
class SymmetricState implements Destroyable {
class SymmetricState implements Destroyable, Cloneable {
// precalculated hash of the Noise name
private static final byte[] INIT_HASH;
private static final byte[] INIT_HASH_XK;
private static final byte[] INIT_HASH_IK;
static {
INIT_HASH_XK = initHash(HandshakeState.protocolName);
INIT_HASH_IK = initHash(HandshakeState.protocolName2);
}
/**
* @since 0.9.44
*/
private static byte[] initHash(String protocolName) {
byte[] protocolNameBytes;
try {
protocolNameBytes = HandshakeState.protocolName.getBytes("UTF-8");
protocolNameBytes = protocolName.getBytes("UTF-8");
} catch (UnsupportedEncodingException e) {
// If UTF-8 is not supported, then we are definitely in trouble!
throw new UnsupportedOperationException("UTF-8 encoding is not supported");
}
INIT_HASH = new byte[32];
byte[] rv = new byte[32];
if (protocolNameBytes.length <= 32) {
System.arraycopy(protocolNameBytes, 0, INIT_HASH, 0, protocolNameBytes.length);
Arrays.fill(INIT_HASH, protocolNameBytes.length, 32, (byte)0);
System.arraycopy(protocolNameBytes, 0, rv, 0, protocolNameBytes.length);
Arrays.fill(rv, protocolNameBytes.length, 32, (byte)0);
} else {
try {
MessageDigest hash = Noise.createHash("SHA256");
hash.update(protocolNameBytes, 0, protocolNameBytes.length);
hash.digest(INIT_HASH, 0, 32);
hash.digest(rv, 0, 32);
} catch (Exception e) {
throw new IllegalStateException(e);
}
}
return rv;
}
private final CipherState cipher;
@ -78,7 +88,7 @@ class SymmetricState implements Destroyable {
* @throws NoSuchAlgorithmException The cipher or hash algorithm in the
* protocol name is not supported.
*/
public SymmetricState(String cipherName, String hashName) throws NoSuchAlgorithmException
public SymmetricState(String cipherName, String hashName, String patternId) throws NoSuchAlgorithmException
{
cipher = Noise.createCipher(cipherName);
hash = Noise.createHash(hashName);
@ -87,10 +97,29 @@ class SymmetricState implements Destroyable {
h = new byte [hashLength];
prev_h = new byte [hashLength];
System.arraycopy(INIT_HASH, 0, h, 0, hashLength);
byte[] initHash;
if (patternId.equals(HandshakeState.PATTERN_ID_XK))
initHash = INIT_HASH_XK;
else if (patternId.equals(HandshakeState.PATTERN_ID_IK))
initHash = INIT_HASH_IK;
else
throw new IllegalArgumentException("Handshake pattern is not recognized");
System.arraycopy(initHash, 0, h, 0, hashLength);
System.arraycopy(h, 0, ck, 0, hashLength);
}
/**
* Copy constructor for cloning
* @since 0.9.44
*/
protected SymmetricState(SymmetricState o) throws CloneNotSupportedException {
cipher = o.cipher.clone();
hash = (MessageDigest) o.hash.clone();
ck = Arrays.copyOf(o.ck, o.ck.length);
h = Arrays.copyOf(o.h, o.h.length);
prev_h = Arrays.copyOf(o.prev_h, o.prev_h.length);
}
/**
* Gets the name of the Noise protocol.
*
@ -482,6 +511,15 @@ class SymmetricState implements Destroyable {
return rv;
}
/**
* I2P
* @since 0.9.44
*/
@Override
public SymmetricState clone() throws CloneNotSupportedException {
return new SymmetricState(this);
}
/**
* I2P debug
*/

View File

@ -4,6 +4,7 @@ import java.util.concurrent.LinkedBlockingQueue;
import net.i2p.I2PAppContext;
import net.i2p.crypto.EncType;
import net.i2p.crypto.KeyFactory;
import net.i2p.crypto.KeyPair;
import net.i2p.data.PrivateKey;
import net.i2p.data.PublicKey;
@ -19,7 +20,7 @@ import net.i2p.util.SystemVersion;
*
* @since 0.9.36 from DHSessionKeyFactory.PrecalcRunner
*/
public class X25519KeyFactory extends I2PThread {
public class X25519KeyFactory extends I2PThread implements KeyFactory {
private final I2PAppContext _context;
private final Log _log;

View File

@ -682,7 +682,7 @@ class InboundEstablishState extends EstablishBase implements NTCP2Payload.Payloa
}
try {
_handshakeState = new HandshakeState(HandshakeState.RESPONDER, _transport.getXDHFactory());
_handshakeState = new HandshakeState(HandshakeState.PATTERN_ID_XK, HandshakeState.RESPONDER, _transport.getXDHFactory());
} catch (GeneralSecurityException gse) {
throw new IllegalStateException("bad proto", gse);
}

View File

@ -106,7 +106,7 @@ class OutboundNTCP2State implements EstablishState {
_state = State.OB_INIT;
_tmp = new byte[TOTAL1_MAX];
try {
_handshakeState = new HandshakeState(HandshakeState.INITIATOR, _transport.getXDHFactory());
_handshakeState = new HandshakeState(HandshakeState.PATTERN_ID_XK, HandshakeState.INITIATOR, _transport.getXDHFactory());
} catch (GeneralSecurityException gse) {
throw new IllegalStateException("bad proto", gse);
}