IETF flavor WIP
This commit is contained in:
@ -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);
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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() {}
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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'),
|
||||
|
@ -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) {
|
||||
|
40
src/main/java/nearenough/util/MJD.java
Normal file
40
src/main/java/nearenough/util/MJD.java
Normal 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;
|
||||
}
|
||||
|
||||
}
|
Reference in New Issue
Block a user