IETF flavor WIP

This commit is contained in:
zzz
2021-04-14 07:22:33 -04:00
parent bc115938be
commit 221e3c28c6
7 changed files with 145 additions and 9 deletions

View File

@ -29,15 +29,18 @@ import java.security.SecureRandom;
import java.security.SignatureException;
import java.util.Arrays;
import java.util.Random;
import nearenough.protocol.RtConstants;
import nearenough.protocol.RtEd25519;
import nearenough.protocol.RtHashing;
import nearenough.protocol.RtMessage;
import nearenough.protocol.RtMessageBuilder;
import nearenough.protocol.RtTag;
import nearenough.protocol.exceptions.InvalidRoughTimeMessage;
import nearenough.protocol.exceptions.MerkleTreeInvalid;
import nearenough.protocol.exceptions.MidpointInvalid;
import nearenough.protocol.exceptions.SignatureInvalid;
import nearenough.util.BytesUtil;
import nearenough.util.MJD;
/**
* Creates RoughTime client requests and processes server responses.
@ -184,10 +187,20 @@ public final class RoughtimeClient {
* @return a {@link RtMessage} populated with a unique nonce (NONC tag)
*/
public RtMessage createRequest() {
return RtMessage.builder()
RtMessageBuilder b = RtMessage.builder()
.addPadding(true)
.add(RtTag.NONC, nonce)
.build();
.add(RtTag.NONC, nonce);
/// should add both in a list
if (RtConstants.USE_VERSION >= 4) {
byte[] v = new byte[4];
BytesUtil.setIntLE(v, 0, RtConstants.VERSION_4);
b.add(RtTag.VER, v);
} else if (RtConstants.USE_VERSION >= 3) {
byte[] v = new byte[4];
BytesUtil.setIntLE(v, 0, RtConstants.VERSION_3);
b.add(RtTag.VER, v);
}
return b.build();
}
/**
@ -236,6 +249,10 @@ public final class RoughtimeClient {
delegatedKey = deleMsg.get(RtTag.PUBK);
delegationMinT = BytesUtil.getLongLE(deleMsg.get(RtTag.MINT), 0);
delegationMaxT = BytesUtil.getLongLE(deleMsg.get(RtTag.MAXT), 0);
if (RtConstants.USE_VERSION >= 3) {
delegationMinT = MJD.fromMJD(delegationMinT);
delegationMaxT = MJD.fromMJD(delegationMaxT);
}
}
/**
@ -287,6 +304,9 @@ public final class RoughtimeClient {
RtMessage srepMsg = RtMessage.fromBytes(srepBytes);
midpoint = BytesUtil.getLongLE(srepMsg.get(RtTag.MIDP), 0);
if (RtConstants.USE_VERSION >= 3) {
midpoint = MJD.fromMJD(midpoint);
}
radius = BytesUtil.getIntLE(srepMsg.get(RtTag.RADI), 0);
validateMidpointBounds(midpoint);
}

View File

@ -24,6 +24,9 @@ import java.net.StandardProtocolFamily;
import java.nio.ByteBuffer;
import java.nio.channels.DatagramChannel;
import java.time.Instant;
import net.i2p.data.Base64;
import nearenough.client.RoughtimeClient;
import nearenough.protocol.RtMessage;
import nearenough.protocol.RtWire;
@ -34,13 +37,22 @@ import nearenough.protocol.RtWire;
public final class NioClient {
// Hostname and port of the public int08h.com Roughtime server
private static final String INT08H_SERVER_HOST = "roughtime.int08h.com";
//private static final String INT08H_SERVER_HOST = "roughtime.int08h.com";
//private static final String INT08H_SERVER_HOST = "roughtime.cloudflare.com";
private static final String INT08H_SERVER_HOST = "localhost";
private static final int INT08H_SERVER_PORT = 2002;
// Long-term public key of the public int08h.com Roughtime server
/*
private static final byte[] INT08H_SERVER_PUBKEY = hexToBytes(
"016e6e0284d24c37c6e4d7d8d5b4e1d3c1949ceaa545bf875616c9dce0c9bec1"
);
*/
private static final byte[] INT08H_SERVER_PUBKEY = Base64.decode(
// "gD63hSj3ScS+wuOeGrubXlq35N1c5Lby/S+T7MNTjxo=", true
"nWKlNmAMMpxJB2E/GXiiy1C4dfPlYBADBFvgHAiJ/Zg=", true
);
@SuppressWarnings("Duplicates")
public static void main(String[] args) throws IOException, InterruptedException {
@ -59,6 +71,7 @@ public final class NioClient {
// Encode for transmission
ByteBuffer encodedRequest = RtWire.toWire(request);
System.out.println(net.i2p.util.HexDump.dump(encodedRequest.array()));
// Send the message
int bytesWritten = channel.send(encodedRequest, addr);
@ -88,9 +101,10 @@ public final class NioClient {
if (recvBuf.hasRemaining()) {
// A reply from the server has been received
System.out.printf("Read message of %d bytes from %s:\n", recvBuf.remaining(), addr);
System.out.println(net.i2p.util.HexDump.dump(recvBuf.array(), 0, recvBuf.remaining()));
// Parse the response
RtMessage response = RtMessage.fromByteBuffer(recvBuf);
RtMessage response = RtWire.fromWire(recvBuf);
System.out.println(response);
// Validate the response. Checks that the message is well-formed, all signatures are valid,

View File

@ -87,6 +87,32 @@ public final class RtConstants {
*/
public static final byte TREE_NODE_TWEAK = 0x01;
/**
* Packet header and msg length = 12
*/
public static final int HEADER_SIZE = 12;
/**
* Packet header, big endian "ROUGHTIM"
*/
public static final long PACKET_HEADER = Long.reverseBytes(0x4d49544847554f52L);
/**
* Protocol version, IETF draft 3, big endian
*/
public static final int VERSION_3 = 0x80000003;
/**
* Protocol version, IETF draft 4, big endian
*/
public static final int VERSION_4 = 0x80000004;
/**
* What version protocol to use?
*/
public static final int USE_VERSION = 4;
// utility class
private RtConstants() {}
}

View File

@ -80,7 +80,10 @@ public final class RtMessageBuilder {
// to end up with a zero-length pad value
int paddingBytes = Math.max(0, (MIN_REQUEST_LENGTH - encodedSize - padOverhead));
byte[] padding = new byte[paddingBytes];
map.put(RtTag.PAD, padding);
if (RtConstants.USE_VERSION >= 3)
map.put(RtTag.PADI, padding);
else
map.put(RtTag.PAD, padding);
}
return new RtMessage(this);

View File

@ -38,7 +38,8 @@ public enum RtTag {
MIDP('M', 'I', 'D', 'P'),
MINT('M', 'I', 'N', 'T'),
NONC('N', 'O', 'N', 'C'),
PAD('P', 'A', 'D', 0xff), // TODO change to 0x00 for IETF
PAD('P', 'A', 'D', 0xff), // Old padding
PADI('P', 'A', 'D', 0x00), // IETF padding
PATH('P', 'A', 'T', 'H'),
PUBK('P', 'U', 'B', 'K'),
RADI('R', 'A', 'D', 'I'),

View File

@ -28,6 +28,29 @@ import java.util.function.ToIntFunction;
*/
public final class RtWire {
/**
* Decode the given message
*
* @return a message
*/
public static RtMessage fromWire(ByteBuffer buf) {
checkNotNull(buf, "buf");
int min = 4;
if (RtConstants.USE_VERSION >= 3)
min += RtConstants.HEADER_SIZE;
checkState(buf.remaining() >= min, "nonsensical output buf size %s", buf.remaining());
if (RtConstants.USE_VERSION >= 3) {
long hdr = buf.getLong();
if (RtConstants.PACKET_HEADER != hdr)
throw new IllegalArgumentException("Bad packet header 0x" + Long.toString(hdr, 16));
int len = Integer.reverseBytes(buf.getInt());
int left = buf.remaining();
if (len != left)
throw new IllegalArgumentException("Bad packet length " + len + " expected " + left);
}
return RtMessage.fromByteBuffer(buf);
}
/**
* Encode the given message for network transmission using the system default ByteBuffer allocator.
*
@ -38,7 +61,15 @@ public final class RtWire {
int encodedSize = computeEncodedSize(msg.mapping());
ByteBuffer buf = ByteBuffer.allocate(encodedSize);
checkState(buf.remaining() >= 4, "nonsensical output buf size %s", buf.remaining());
int min = 4;
if (RtConstants.USE_VERSION >= 3)
min += RtConstants.HEADER_SIZE;
checkState(buf.remaining() >= min, "nonsensical output buf size %s", buf.remaining());
if (RtConstants.USE_VERSION >= 3) {
buf.putLong(RtConstants.PACKET_HEADER);
buf.putInt(Integer.reverseBytes(encodedSize - RtConstants.HEADER_SIZE));
}
writeNumTags(msg, buf);
writeOffsets(msg, buf);
@ -56,6 +87,7 @@ public final class RtWire {
* @return The size in bytes of the on-the-wire encoding of the provided {@code map}.
*/
/*package*/ static int computeEncodedSize(Map<RtTag, byte[]> map) {
int hdrSum = RtConstants.USE_VERSION >= 3 ? RtConstants.HEADER_SIZE : 0;
int numTagsSum = 4;
int tagsSum = 4 * map.size();
int offsetsSum = map.size() < 2 ? 0 : (4 * (map.size() - 1));
@ -66,7 +98,7 @@ public final class RtWire {
}
}).sum();
return numTagsSum + tagsSum + offsetsSum + valuesSum;
return hdrSum + numTagsSum + tagsSum + offsetsSum + valuesSum;
}
private static void writeNumTags(RtMessage msg, ByteBuffer buf) {

View File

@ -0,0 +1,40 @@
package nearenough.util;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.TimeZone;
/**
* Utility methods for Modified Julian Dates.
*/
public final class MJD {
private static final long MJD_TO_UNIX_DAYS = 40587;
/**
* @param mjd big endian, 3 bytes day, 5 bytes microsec
* @return java ms
*/
public static long fromMJD(long mjd) {
long day = (mjd >> 40) & 0xffffffL;
long microsec = mjd & 0xffffffffffL;
Calendar cal = GregorianCalendar.getInstance(TimeZone.getTimeZone("GMT"));
cal.setTimeInMillis(0);
cal.add(Calendar.DAY_OF_MONTH, (int) (day - MJD_TO_UNIX_DAYS));
cal.add(Calendar.MILLISECOND, (int) ((microsec + 500) / 1000));
return cal.getTimeInMillis();
}
/**
* @param time java ms
* @return big endian, 3 bytes day, 5 bytes microsec
*/
public static long toMJD(long time) {
Calendar cal = GregorianCalendar.getInstance(TimeZone.getTimeZone("GMT"));
cal.setTimeInMillis(time);
// TODO if needed
return time;
}
}