Ratchet: WIP on NSR send/recv

This commit is contained in:
zzz
2019-11-01 12:22:20 +00:00
parent 71411be6d9
commit 0cd8073f39
5 changed files with 163 additions and 127 deletions

View File

@ -150,10 +150,9 @@ public final class ECIESAEADEngine {
_log.debug("Decrypting existing session encrypted with tag: " + st.toString() + ": key: " + key.toBase64() + ": " + data.length + " bytes " /* + Base64.encode(data, 0, 64) */ );
HandshakeState state = key.getHandshakeState();
if (state != null) {
if (state == null) {
decrypted = decryptExistingSession(tag, data, key, targetPrivateKey);
} else if (data.length >= MIN_NSR_SIZE) {
/** TODO find the state
try {
state = state.clone();
} catch (CloneNotSupportedException e) {
@ -161,9 +160,7 @@ public final class ECIESAEADEngine {
_log.warn("ECIES decrypt fail: clone()", e);
return null;
}
decrypted = decryptNewSessionReply(tag, data, state);
**/
decrypted = null;
decrypted = decryptNewSessionReply(tag, data, state, keyManager);
} else {
decrypted = null;
if (_log.shouldWarn())
@ -180,7 +177,7 @@ public final class ECIESAEADEngine {
}
} else if (data.length >= MIN_NS_SIZE) {
if (shouldDebug) _log.debug("IB Tag " + st + " not found, trying NS decrypt");
decrypted = decryptNewSession(data, targetPrivateKey);
decrypted = decryptNewSession(data, targetPrivateKey, keyManager);
if (decrypted != null) {
if (shouldDebug) _log.debug("NS decrypt success");
_context.statManager().updateFrequency("crypto.eciesAEAD.decryptNewSession");
@ -216,7 +213,7 @@ public final class ECIESAEADEngine {
* @param data 96 bytes minimum
* @return null if decryption fails
*/
private CloveSet decryptNewSession(byte data[], PrivateKey targetPrivateKey)
private CloveSet decryptNewSession(byte data[], PrivateKey targetPrivateKey, RatchetSKM keyManager)
throws DataFormatException {
HandshakeState state;
try {
@ -277,6 +274,11 @@ public final class ECIESAEADEngine {
} catch (Exception e) {
throw new DataFormatException("Msg 1 payload error", e);
}
// tell the SKM
PublicKey bob = new PublicKey(EncType.ECIES_X25519, bobPK);
keyManager.createSession(bob, state);
if (pc.cloveSet.isEmpty()) {
if (_log.shouldWarn())
_log.warn("No garlic block in NS payload");
@ -309,7 +311,7 @@ public final class ECIESAEADEngine {
* @param state must have already been cloned
* @return null if decryption fails
*/
private CloveSet decryptNewSessionReply(byte[] tag, byte[] data, HandshakeState state)
private CloveSet decryptNewSessionReply(byte[] tag, byte[] data, HandshakeState state, RatchetSKM keyManager)
throws DataFormatException {
// part 1 - handshake
byte[] yy = new byte[KEYLEN];
@ -370,8 +372,10 @@ public final class ECIESAEADEngine {
} catch (Exception e) {
throw new DataFormatException("NSR payload error", e);
}
RatchetTagSet tagset_ab = new RatchetTagSet(_hkdf, new SessionKey(ck), new SessionKey(k_ab), 0, 0);
RatchetTagSet tagset_ba = new RatchetTagSet(_hkdf, null, new SessionKey(ck), new SessionKey(k_ba), 0, 0, 5, 5);
long now = _context.clock().now();
RatchetTagSet tagset_ab = new RatchetTagSet(_hkdf, new SessionKey(ck), new SessionKey(k_ab), now, 0);
RatchetTagSet tagset_ba = new RatchetTagSet(_hkdf, keyManager, new SessionKey(ck), new SessionKey(k_ba), now, 0, 5, 5);
// tell the SKM
if (pc.cloveSet.isEmpty()) {
if (_log.shouldWarn())
_log.warn("No garlic block in NSR payload");
@ -516,13 +520,7 @@ public final class ECIESAEADEngine {
_log.debug("Encrypting as NS to " + target);
return encryptNewSession(cloves, target, priv, keyManager);
}
////
byte[] tagsetkey = new byte[32];
/*
byte[] ck = state.getChainingKey();
_hkdf.calculate(ck, ZEROLEN, INFO_0, tagsetkey);
RatchetTagSet tagset = new RatchetTagSet(_context, new SessionKey(ck), new SessionKey(tagsetkey), 0, 0);
*/
HandshakeState state = re.key.getHandshakeState();
if (state != null) {
try {
@ -533,8 +531,12 @@ public final class ECIESAEADEngine {
return null;
}
// register state with skm
return encryptNewSessionReply(cloves, state, re.tag);
if (_log.shouldDebug())
_log.debug("Encrypting as NSR to " + target + " with tag " + re.tag);
return encryptNewSessionReply(cloves, state, re.tag, keyManager);
}
if (_log.shouldDebug())
_log.debug("Encrypting as ES to " + target + " with key " + re.key + " and tag " + re.tag);
byte rv[] = encryptExistingSession(cloves, target, re.key, re.tag);
return rv;
}
@ -589,11 +591,8 @@ public final class ECIESAEADEngine {
}
eph.getEncodedPublicKey(enc, 0);
// save for tagset HKDF
byte[] ck = state.getChainingKey();
// register state with skm
// keyManager.tagsDelivered(state);
// todo
// tell the SKM
keyManager.createSession(target, state);
return enc;
}
@ -616,7 +615,8 @@ public final class ECIESAEADEngine {
* @param state must have already been cloned
* @return encrypted data or null on failure
*/
private byte[] encryptNewSessionReply(CloveSet cloves, HandshakeState state, RatchetSessionTag currentTag) {
private byte[] encryptNewSessionReply(CloveSet cloves, HandshakeState state,
RatchetSessionTag currentTag, RatchetSKM keyManager) {
byte[] tag = currentTag.getData();
state.mixHash(tag, 0, TAGLEN);
@ -666,9 +666,10 @@ public final class ECIESAEADEngine {
_log.warn("Encrypt fail NSR part 2", gse);
return null;
}
RatchetTagSet tagset_ab = new RatchetTagSet(_hkdf, new SessionKey(ck), new SessionKey(k_ab), 0, 0);
/// lsnr
RatchetTagSet tagset_ba = new RatchetTagSet(_hkdf, null, new SessionKey(ck), new SessionKey(k_ba), 0, 0, 5, 5);
long now = _context.clock().now();
RatchetTagSet tagset_ab = new RatchetTagSet(_hkdf, new SessionKey(ck), new SessionKey(k_ab), now, 0);
RatchetTagSet tagset_ba = new RatchetTagSet(_hkdf, keyManager, new SessionKey(ck), new SessionKey(k_ba), now, 0, 5, 5);
// tell the SKM
return enc;
}
@ -751,7 +752,7 @@ public final class ECIESAEADEngine {
public void gotGarlic(GarlicClove clove) {
if (_log.shouldDebug())
_log.debug("Got GARLIC block");
_log.debug("Got GARLIC block: " + clove);
cloveSet.add(clove);
}

View File

@ -32,44 +32,48 @@ public class MuxedSKM extends SessionKeyManager {
public RatchetSKM getECSKM() { return _ec; }
/**
* ElG only
*/
@Override
public SessionKey getCurrentKey(PublicKey target) {
EncType type = target.getType();
if (type == EncType.ELGAMAL_2048)
return _elg.getCurrentKey(target);
if (type == EncType.ECIES_X25519)
return _ec.getCurrentKey(target);
return null;
}
/**
* ElG only
*/
@Override
public SessionKey getCurrentOrNewKey(PublicKey target) {
EncType type = target.getType();
if (type == EncType.ELGAMAL_2048)
return _elg.getCurrentOrNewKey(target);
if (type == EncType.ECIES_X25519)
return _ec.getCurrentOrNewKey(target);
return null;
}
/**
* ElG only
*/
@Override
public void createSession(PublicKey target, SessionKey key) {
EncType type = target.getType();
if (type == EncType.ELGAMAL_2048)
_elg.createSession(target, key);
else if (type == EncType.ECIES_X25519)
_ec.createSession(target, key);
else
throw new IllegalArgumentException();
}
/**
* ElG only
*/
@Override
public SessionKey createSession(PublicKey target) {
EncType type = target.getType();
if (type == EncType.ELGAMAL_2048)
return _elg.createSession(target);
if (type == EncType.ECIES_X25519)
return _ec.createSession(target);
return null;
}

View File

@ -15,4 +15,9 @@ class RatchetEntry {
this.tag = tag;
this.key = key;
}
@Override
public String toString() {
return "RatchetEntry[" + tag + ' ' + key + ']';
}
}

View File

@ -41,7 +41,7 @@ import net.i2p.util.SimpleTimer;
public class RatchetSKM extends SessionKeyManager implements SessionTagListener {
private final Log _log;
/** Map allowing us to go from the targeted PublicKey to the OutboundSession used */
private final Map<PublicKey, OutboundSession> _outboundSessions;
private final ConcurrentHashMap<PublicKey, OutboundSession> _outboundSessions;
/** Map allowing us to go from a SessionTag to the containing RatchetTagSet */
private final ConcurrentHashMap<RatchetSessionTag, RatchetTagSet> _inboundTagSets;
protected final I2PAppContext _context;
@ -74,6 +74,8 @@ public class RatchetSKM extends SessionKeyManager implements SessionTagListener
private static final int MIN_RCV_WINDOW = 20;
private static final int MAX_RCV_WINDOW = 50;
private static final byte[] ZEROLEN = new byte[0];
private static final String INFO_0 = "SessionReplyTags";
/**
@ -86,7 +88,7 @@ public class RatchetSKM extends SessionKeyManager implements SessionTagListener
super(context);
_log = context.logManager().getLog(RatchetSKM.class);
_context = context;
_outboundSessions = new HashMap<PublicKey, OutboundSession>(64);
_outboundSessions = new ConcurrentHashMap<PublicKey, OutboundSession>(64);
_inboundTagSets = new ConcurrentHashMap<RatchetSessionTag, RatchetTagSet>(128);
_hkdf = new HKDF(context);
// start the precalc of Elg2 keys if it wasn't already started
@ -99,9 +101,7 @@ public class RatchetSKM extends SessionKeyManager implements SessionTagListener
public void shutdown() {
_alive = false;
_inboundTagSets.clear();
synchronized (_outboundSessions) {
_outboundSessions.clear();
}
_outboundSessions.clear();
}
private class CleanupEvent implements SimpleTimer.TimedEvent {
@ -123,84 +123,63 @@ public class RatchetSKM extends SessionKeyManager implements SessionTagListener
/** OutboundSession - used only by HTML */
private Set<OutboundSession> getOutboundSessions() {
synchronized (_outboundSessions) {
return new HashSet<OutboundSession>(_outboundSessions.values());
}
return new HashSet<OutboundSession>(_outboundSessions.values());
}
/**
* Retrieve the session key currently associated with encryption to the target,
* or null if a new session key should be generated.
*
* Warning - don't generate a new session if this returns null, it's racy, use getCurrentOrNewKey()
* @throws UnsupportedOperationException always
*/
@Override
public SessionKey getCurrentKey(PublicKey target) {
OutboundSession sess = getSession(target);
if (sess == null) return null;
long now = _context.clock().now();
if (sess.getLastUsedDate() < now - SESSION_LIFETIME_MAX_MS) {
if (_log.shouldInfo())
_log.info("Expiring old session key established on "
+ new Date(sess.getEstablishedDate())
+ " but not used for "
+ (now-sess.getLastUsedDate())
+ "ms with target " + toString(target));
return null;
}
return sess.getCurrentKey();
throw new UnsupportedOperationException();
}
/**
* Retrieve the session key currently associated with encryption to the target.
* Generates a new session and session key if not previously exising.
*
* @return non-null
* @throws UnsupportedOperationException always
*/
@Override
public SessionKey getCurrentOrNewKey(PublicKey target) {
synchronized (_outboundSessions) {
OutboundSession sess = _outboundSessions.get(target);
if (sess != null) {
long now = _context.clock().now();
if (sess.getLastUsedDate() < now - SESSION_LIFETIME_MAX_MS)
sess = null;
}
if (sess == null) {
SessionKey key = _context.keyGenerator().generateSessionKey();
createAndReturnSession(target, key);
return key;
}
return sess.getCurrentKey();
}
throw new UnsupportedOperationException();
}
/**
* Associate a new session key with the specified target. Metrics to determine
* when to expire that key begin with this call.
*
* Racy if called after getCurrentKey() to check for a current session;
* use getCurrentOrNewKey() in that case.
* @throws UnsupportedOperationException always
*/
@Override
public void createSession(PublicKey target, SessionKey key) {
createAndReturnSession(target, key);
throw new UnsupportedOperationException();
}
/**
* Same as above but for internal use, returns OutboundSession so we don't have
* to do a subsequent getSession()
* Inbound or outbound. Checks state.getRole() to determine.
* For outbound (NS sent), adds to list of pending inbound sessions and returns true.
* For inbound (NS rcvd), if no other pending outbound sessions, creates one
* and returns true, or false if one already exists.
*
*/
private OutboundSession createAndReturnSession(PublicKey target, SessionKey key) {
boolean createSession(PublicKey target, HandshakeState state) {
EncType type = target.getType();
if (type != EncType.ECIES_X25519)
throw new IllegalArgumentException("Bad public key type " + type);
if (_log.shouldInfo())
_log.info("New OB session, sesskey: " + key + " target: " + toString(target));
OutboundSession sess = new OutboundSession(_context, _log, target, key);
addSession(sess);
return sess;
boolean isInbound = state.getRole() == HandshakeState.RESPONDER;
if (isInbound) {
// we are Bob, NS received
OutboundSession sess = new OutboundSession(target, null, state);
boolean rv = addSession(sess);
if (_log.shouldInfo()) {
if (rv)
_log.info("New OB session as Bob. Alice: " + toString(target));
else
_log.info("Dup OB session as Bob. Alice: " + toString(target));
}
return rv;
} else {
// we are Alice, NS sent
OutboundSession sess = new OutboundSession(target, null, state);
if (_log.shouldInfo())
_log.info("New OB session as Alice. Bob: " + toString(target));
return true;
}
}
/**
@ -232,7 +211,14 @@ public class RatchetSKM extends SessionKeyManager implements SessionTagListener
_log.debug("No OB session to " + toString(target));
return null;
}
return sess.consumeNext();
RatchetEntry rv = sess.consumeNext();
if (_log.shouldDebug()) {
if (rv != null)
_log.debug("Using next key/tag " + rv + " to " + toString(target));
else
_log.debug("No more tags in OB session to " + toString(target));
}
return rv;
}
/**
@ -314,7 +300,8 @@ public class RatchetSKM extends SessionKeyManager implements SessionTagListener
if (sess == null) {
if (_log.shouldWarn())
_log.warn("No session for delivered RatchetTagSet to target: " + toString(target));
sess = createAndReturnSession(target, key);
///////////
createSession(target, key);
} else {
sess.setCurrentKey(key);
}
@ -419,6 +406,8 @@ public class RatchetSKM extends SessionKeyManager implements SessionTagListener
}
/**
* Inbound.
*
* Determine if we have received a session key associated with the given session tag,
* and if so, discard it and return the decryption
* key it was received with (via tagsReceived(...)). returns null if no session key
@ -451,23 +440,21 @@ public class RatchetSKM extends SessionKeyManager implements SessionTagListener
}
private OutboundSession getSession(PublicKey target) {
synchronized (_outboundSessions) {
return _outboundSessions.get(target);
}
return _outboundSessions.get(target);
}
private void addSession(OutboundSession sess) {
synchronized (_outboundSessions) {
_outboundSessions.put(sess.getTarget(), sess);
}
/**
*
* @return true if added
*/
private boolean addSession(OutboundSession sess) {
OutboundSession old = _outboundSessions.putIfAbsent(sess.getTarget(), sess);
return old == null;
}
private void removeSession(PublicKey target) {
if (target == null) return;
OutboundSession session = null;
synchronized (_outboundSessions) {
session = _outboundSessions.remove(target);
}
OutboundSession session = _outboundSessions.remove(target);
if ( (session != null) && (_log.shouldWarn()) )
_log.warn("Removing session tags with " + session.availableTags() + " available for "
+ (session.getLastExpirationDate()-_context.clock().now())
@ -572,8 +559,11 @@ public class RatchetSKM extends SessionKeyManager implements SessionTagListener
buf.append("<tr class=\"debug_outboundtarget\"><td><div class=\"debug_targetinfo\"><b>Target public key:</b> ").append(toString(sess.getTarget())).append("<br>" +
"<b>Established:</b> ").append(DataHelper.formatDuration2(now - sess.getEstablishedDate())).append(" ago<br>" +
"<b>Ack Received?</b> ").append(sess.getAckReceived()).append("<br>" +
"<b>Last Used:</b> ").append(DataHelper.formatDuration2(now - sess.getLastUsedDate())).append(" ago<br>" +
"<b>Session key:</b> ").append(sess.getCurrentKey().toBase64()).append("</div></td>" +
"<b>Last Used:</b> ").append(DataHelper.formatDuration2(now - sess.getLastUsedDate())).append(" ago<br>");
SessionKey sk = sess.getCurrentKey();
if (sk != null)
buf.append("<b>Session key:</b> ").append(sk.toBase64());
buf.append("</div></td>" +
"<td><b># Sets:</b> ").append(sess.getTagSets().size()).append("</td></tr>" +
"<tr><td colspan=\"2\"><ul>");
for (Iterator<RatchetTagSet> siter = sets.iterator(); siter.hasNext();) {
@ -621,9 +611,7 @@ public class RatchetSKM extends SessionKeyManager implements SessionTagListener
/**
* The state for a crypto session to a single public key
*/
private static class OutboundSession {
private final I2PAppContext _context;
private final Log _log;
private class OutboundSession {
private final PublicKey _target;
private SessionKey _currentKey;
private final long _established;
@ -657,15 +645,37 @@ public class RatchetSKM extends SessionKeyManager implements SessionTagListener
private static final int MAX_FAILS = 2;
public OutboundSession(I2PAppContext ctx, Log log, PublicKey target, SessionKey key) {
_context = ctx;
_log = log;
public OutboundSession(PublicKey target, SessionKey key, HandshakeState state) {
_target = target;
_currentKey = key;
_established = ctx.clock().now();
_established = _context.clock().now();
_lastUsed = _established;
_unackedTagSets = new HashSet<RatchetTagSet>(4);
_tagSets = new ArrayList<RatchetTagSet>(6);
// generate expected tagset
byte[] ck = state.getChainingKey();
byte[] tagsetkey = new byte[32];
_hkdf.calculate(ck, ZEROLEN, INFO_0, tagsetkey);
boolean isInbound = state.getRole() == HandshakeState.RESPONDER;
SessionKey rk = new SessionKey(ck);
SessionKey tk = new SessionKey(tagsetkey);
if (isInbound) {
// This is an INBOUND NS, we make an OUTBOUND tagset for the NSR
RatchetTagSet tagset = new RatchetTagSet(_hkdf, state,
rk, tk,
_established, 0);
_tagSets.add(tagset);
if (_log.shouldDebug())
_log.debug("New OB Session, rk = " + rk + " tk = " + tk + " 1st tagset: " + tagset);
} else {
// This is an OUTBOUND NS, we make an INBOUND tagset for the NSR
RatchetTagSet tagset = new RatchetTagSet(_hkdf, RatchetSKM.this, state,
rk, tk,
_established, 0, 5, 5);
_unackedTagSets.add(tagset);
if (_log.shouldDebug())
_log.debug("New IB Session, rk = " + rk + " tk = " + tk + " 1st tagset: " + tagset);
}
}
/**

View File

@ -60,7 +60,17 @@ class RatchetTagSet implements TagSetHandle {
private static final int TAGLEN = 8;
/**
* Outbound Tagset
* Outbound NSR Tagset
*
* @param date For outbound: creation time
*/
public RatchetTagSet(HKDF hkdf, HandshakeState state, SessionKey rootKey, SessionKey data,
long date, int id) {
this(hkdf, null, state, rootKey, data, date, id, false, 0, 0);
}
/**
* Outbound ES Tagset
*
* @param date For outbound: creation time
*/
@ -125,8 +135,6 @@ class RatchetTagSet implements TagSetHandle {
_sessionTags = null;
_sessionKeys = null;
}
System.out.println("DH INIT, rootKey = " + rootKey.toBase64() +
" data = " + data.toBase64());
}
public void clear() {
@ -296,6 +304,10 @@ class RatchetTagSet implements TagSetHandle {
* @return a key and nonce, non-null
*/
public SessionKeyAndNonce consumeNextKey() {
// NSR
if (_state != null)
return new SessionKeyAndNonce(_state);
// ES
byte[] key = new byte[32];
hkdf.calculate(_symmkey_ck, _symmkey_constant, INFO_5, _symmkey_ck, key, 0);
_lastKey++;
@ -325,15 +337,19 @@ class RatchetTagSet implements TagSetHandle {
buf.append(" Size: ").append(sz);
buf.append('/').append(getOriginalSize());
buf.append(" Acked? ").append(_acked);
for (int i = 0; i < sz; i++) {
int n = _sessionTags.keyAt(i);
RatchetSessionTag tag = _sessionTags.valueAt(i);
byte[] key = _sessionKeys.get(n);
buf.append("\n " + n + '\t' + Base64.encode(tag.getData()));
if (key != null)
buf.append('\t' + Base64.encode(key));
else
buf.append("\tdeferred");
if (_sessionTags != null) {
for (int i = 0; i < sz; i++) {
int n = _sessionTags.keyAt(i);
RatchetSessionTag tag = _sessionTags.valueAt(i);
buf.append("\n " + n + '\t' + Base64.encode(tag.getData()));
if (_sessionKeys != null) {
byte[] key = _sessionKeys.get(n);
if (key != null)
buf.append('\t' + Base64.encode(key));
else
buf.append("\tdeferred");
}
}
}
return buf.toString();
}