Update to reflect client-only status.
This commit is contained in:
19
README.md
19
README.md
@ -1,7 +1,6 @@
|
||||
# Nearenough
|
||||
**Nearenough** is a Java implementation of the
|
||||
[Roughtime](https://roughtime.googlesource.com/roughtime) secure time synchronization protocol. It
|
||||
provides both a Roughtime client and a (work-in-progress) Roughtime server.
|
||||
**Nearenough** is a Java client of the [Roughtime](https://roughtime.googlesource.com/roughtime)
|
||||
secure time synchronization protocol.
|
||||
|
||||
Nearenough aims to be 100% interoperable with the wider Roughtime ecosystem.
|
||||
|
||||
@ -19,7 +18,7 @@ created by Adam Langley and Robert Obryk.
|
||||
Nearenough bundles all required dependencies in the `lib` directory. Add those `.jar` files to
|
||||
your IDE's project classpath. Building is IDE-only for the moment.
|
||||
|
||||
## Client Quickstart
|
||||
## Quickstart
|
||||
|
||||
### Client Examples
|
||||
See [`examples/NioClient.java`](../master/examples/NioClient.java) and
|
||||
@ -57,22 +56,16 @@ if (client.isResponseValid()) {
|
||||
See the javadocs in [`RoughtimeClient.java`](../master/src/nearenough/client/RoughtimeClient.java)
|
||||
for more information.
|
||||
|
||||
## Server Quickstart
|
||||
|
||||
Server is a work-in-progress. Check back!
|
||||
|
||||
## Implementation Status
|
||||
Nearenough is not stable yet. Expect significant changes as the code evolves.
|
||||
Nearenough is stable.
|
||||
|
||||
* Protocol - Client protocol is feature complete. Server protocol needs some work, mostly around
|
||||
Merkle tree handling.
|
||||
* Protocol - Client protocol is feature complete.
|
||||
* Client - Feature complete except for ecosystem-style request chaining.
|
||||
* Server - Just getting started
|
||||
|
||||
## Contributors
|
||||
* Stuart Stock, original author and current maintainer (stuart {at} int08h.com)
|
||||
|
||||
If you would like to contribute to Nearenough, please see the guidlines in
|
||||
If you would like to contribute to Nearenough, please see the guidelines in
|
||||
[CONTRIBUTING.md](../master/CONTRIBUTING.md).
|
||||
|
||||
## Copyright and License
|
||||
|
@ -2,9 +2,9 @@ Features and changes under consideration. Not necessarily in order.
|
||||
|
||||
* Mutation testing using PIT http://pitest.org/
|
||||
|
||||
* Remove Netty dependency in client and protocol. While server will always need Netty, having a
|
||||
dependency-free client and protocol seems worthwhile as it opens up the possibility of use
|
||||
on Android. This would mean using either ByteBuffer or byte[] in place of ByteBuf.
|
||||
* Remove Netty dependency in client and protocol. Having a dependency-free client and protocol seems
|
||||
worthwhile as it opens up the possibility of use on Android. This would mean using either
|
||||
ByteBuffer or byte[] in place of ByteBuf.
|
||||
|
||||
* Further towards potential use on Android, remove the few Java 8 features from the codebase. This
|
||||
only makes sense if above the client and protocol changes are made first.
|
||||
@ -21,5 +21,5 @@ Features and changes under consideration. Not necessarily in order.
|
||||
Non-features, will not be implemented:
|
||||
|
||||
* Logging in the client and protocol. I have little desire to wade into the tire fire that is Java
|
||||
logging. My stance is that it's a choice client code should make. TBD on the server code.
|
||||
logging. My stance is that it's a choice client code should make.
|
||||
|
||||
|
@ -1,177 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2017 int08h LLC. All rights reserved.
|
||||
*
|
||||
* int08h LLC licenses Nearenough (the "Software") to you under the Apache License, version 2.0
|
||||
* (the "License"); you may not use this Software except in compliance with the License. You may
|
||||
* obtain a copy of the License from the LICENSE file included with the Software or at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the
|
||||
* License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
|
||||
* either express or implied. See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package nearenough.server;
|
||||
|
||||
import static nearenough.protocol.RtConstants.MIN_SEED_LENGTH;
|
||||
import static nearenough.util.Preconditions.checkArgument;
|
||||
import static nearenough.util.Preconditions.checkNotNull;
|
||||
import static nearenough.util.Preconditions.checkState;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.SecureRandom;
|
||||
import java.security.SignatureException;
|
||||
import java.time.Duration;
|
||||
import java.util.Random;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import nearenough.protocol.RtEd25519;
|
||||
import nearenough.protocol.RtMessage;
|
||||
import nearenough.protocol.RtTag;
|
||||
import nearenough.protocol.RtWire;
|
||||
import nearenough.server.clock.ClockSource;
|
||||
import nearenough.server.clock.SystemClock;
|
||||
import nearenough.util.BytesUtil;
|
||||
|
||||
/**
|
||||
* A long-term server Ed25519 key pair. Provides convenient delegated certificate generation and
|
||||
* rotation.
|
||||
*/
|
||||
public final class LongTermKey {
|
||||
|
||||
private final RtEd25519.Signer longTermKey;
|
||||
private final Duration delegationDuration;
|
||||
private final Random random;
|
||||
private final ClockSource clock;
|
||||
|
||||
private RtEd25519.Signer delegatedKey;
|
||||
private long delegationStart;
|
||||
private long delegationEnd;
|
||||
|
||||
public LongTermKey(byte[] seed) throws SignatureException, InvalidKeyException {
|
||||
this(seed, Duration.ofDays(7), new SystemClock());
|
||||
}
|
||||
|
||||
public LongTermKey(byte[] seed, Duration delegationDuration, ClockSource clock)
|
||||
throws SignatureException, InvalidKeyException
|
||||
{
|
||||
checkArgument((seed != null) && (seed.length >= MIN_SEED_LENGTH), "invalid private seed");
|
||||
checkArgument(
|
||||
(delegationDuration != null) && (delegationDuration.getSeconds() > 60),
|
||||
"invalid delegation duration, must be >60 seconds"
|
||||
);
|
||||
checkNotNull(clock, "clock");
|
||||
|
||||
this.longTermKey = new RtEd25519.Signer(seed);
|
||||
this.delegationDuration = delegationDuration;
|
||||
this.random = new SecureRandom();
|
||||
this.clock = clock;
|
||||
|
||||
newDelegatedKey();
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a new delegated key pair
|
||||
*/
|
||||
public void newDelegatedKey() throws SignatureException, InvalidKeyException {
|
||||
byte[] nonce = new byte[MIN_SEED_LENGTH];
|
||||
random.nextBytes(nonce);
|
||||
delegatedKey = new RtEd25519.Signer(nonce);
|
||||
delegationStart = clock.now();
|
||||
delegationEnd = delegationStart + TimeUnit.SECONDS.toMillis(delegationDuration.getSeconds());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return A CERT message composed of:
|
||||
* <ol>
|
||||
* <li>DELE containing current delegated PUBK along with its MINT and MAXT time bounds</li>
|
||||
* <li>SIG of the DELE message using the long-term key</li>
|
||||
* </ol>
|
||||
*/
|
||||
public RtMessage asCertMessage() throws SignatureException {
|
||||
byte[] minT = new byte[8];
|
||||
BytesUtil.setLongLE(minT, 0, delegationStart);
|
||||
|
||||
byte[] maxT = new byte[8];
|
||||
BytesUtil.setLongLE(maxT, 0, delegationEnd);
|
||||
|
||||
RtMessage deleMsg = RtMessage.builder()
|
||||
.add(RtTag.PUBK, delegatedPublicKey())
|
||||
.add(RtTag.MINT, minT)
|
||||
.add(RtTag.MAXT, maxT)
|
||||
.build();
|
||||
|
||||
ByteBuf buf = RtWire.toWire(deleMsg);
|
||||
byte[] deleBytes = new byte[buf.readableBytes()];
|
||||
buf.readBytes(deleBytes);
|
||||
|
||||
byte[] sigBytes = signLongTermKey(deleBytes);
|
||||
|
||||
return RtMessage.builder()
|
||||
.add(RtTag.SIG, sigBytes)
|
||||
.add(RtTag.DELE, deleBytes)
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The long-term public key.
|
||||
*/
|
||||
public byte[] longTermPublicKey() {
|
||||
return longTermKey.getPubKey();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The public key of the current delegated key.
|
||||
*/
|
||||
public byte[] delegatedPublicKey() {
|
||||
return delegatedKey.getPubKey();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Epoch milliseconds starting point of the delegated key's validity
|
||||
*/
|
||||
public long delegationStart() {
|
||||
return delegationStart;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Epoch milliseconds end point of the delegated key's validity
|
||||
*/
|
||||
public long delegationEnd() {
|
||||
return delegationEnd;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sign the provided content using the long-term key
|
||||
*
|
||||
* @param content Content to be signed
|
||||
* @return Ed25519 signature of content using the long-term key
|
||||
*
|
||||
* @throws SignatureException If signing operation fails
|
||||
*/
|
||||
public byte[] signLongTermKey(byte[] content) throws SignatureException {
|
||||
longTermKey.update(content);
|
||||
return longTermKey.sign();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sign the provided content using the current delegated key
|
||||
*
|
||||
* @param content Content to be signed
|
||||
* @return Ed25519 signature of content using the current delegated key
|
||||
*
|
||||
* @throws SignatureException If signing operation fails
|
||||
*/
|
||||
public byte[] signDelegatedKey(byte[] content) throws SignatureException {
|
||||
long now = clock.now();
|
||||
checkState(
|
||||
(now >= delegationStart) && (now <= delegationEnd),
|
||||
"current time is outside delegated key bounds"
|
||||
);
|
||||
|
||||
delegatedKey.update(content);
|
||||
return delegatedKey.sign();
|
||||
}
|
||||
}
|
@ -1,28 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2017 int08h LLC. All rights reserved.
|
||||
*
|
||||
* int08h LLC licenses Nearenough (the "Software") to you under the Apache License, version 2.0
|
||||
* (the "License"); you may not use this Software except in compliance with the License. You may
|
||||
* obtain a copy of the License from the LICENSE file included with the Software or at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the
|
||||
* License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
|
||||
* either express or implied. See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package nearenough.server.clock;
|
||||
|
||||
/**
|
||||
* A ClockSource provides milliseonds since the epoch
|
||||
*/
|
||||
public interface ClockSource {
|
||||
|
||||
/**
|
||||
* @return UTC milliseconds since the epoch
|
||||
*/
|
||||
long now();
|
||||
|
||||
}
|
@ -1,27 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2017 int08h LLC. All rights reserved.
|
||||
*
|
||||
* int08h LLC licenses Nearenough (the "Software") to you under the Apache License, version 2.0
|
||||
* (the "License"); you may not use this Software except in compliance with the License. You may
|
||||
* obtain a copy of the License from the LICENSE file included with the Software or at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the
|
||||
* License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
|
||||
* either express or implied. See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package nearenough.server.clock;
|
||||
|
||||
/**
|
||||
* A {@link ClockSource} that uses {@link System#currentTimeMillis()}
|
||||
*/
|
||||
public class SystemClock implements ClockSource {
|
||||
|
||||
@Override
|
||||
public long now() {
|
||||
return System.currentTimeMillis();
|
||||
}
|
||||
}
|
@ -1,51 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2017 int08h LLC. All rights reserved.
|
||||
*
|
||||
* int08h LLC licenses Nearenough (the "Software") to you under the Apache License, version 2.0
|
||||
* (the "License"); you may not use this Software except in compliance with the License. You may
|
||||
* obtain a copy of the License from the LICENSE file included with the Software or at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the
|
||||
* License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
|
||||
* either express or implied. See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package nearenough.server.clock;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* A {@link ClockSource} to be used for tests
|
||||
*/
|
||||
public final class TestingClock implements ClockSource {
|
||||
|
||||
private long now;
|
||||
|
||||
public TestingClock() {
|
||||
this.now = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
public TestingClock(long now) {
|
||||
this.now = now;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long now() {
|
||||
return now;
|
||||
}
|
||||
|
||||
public void setNow(long now) {
|
||||
this.now = now;
|
||||
}
|
||||
|
||||
public void advance(int amount, TimeUnit unit) {
|
||||
this.now += unit.toMillis(amount);
|
||||
}
|
||||
|
||||
public void decrement(int amount, TimeUnit unit) {
|
||||
this.now -= unit.toMillis(amount);
|
||||
}
|
||||
}
|
@ -1,140 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2017 int08h LLC. All rights reserved.
|
||||
*
|
||||
* int08h LLC licenses Nearenough (the "Software") to you under the Apache License, version 2.0
|
||||
* (the "License"); you may not use this Software except in compliance with the License. You may
|
||||
* obtain a copy of the License from the LICENSE file included with the Software or at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the
|
||||
* License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
|
||||
* either express or implied. See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package nearenough.server;
|
||||
|
||||
import static nearenough.protocol.RtConstants.MIN_SEED_LENGTH;
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.hamcrest.Matchers.greaterThan;
|
||||
import static org.hamcrest.Matchers.lessThan;
|
||||
import static org.junit.Assert.assertArrayEquals;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.Arrays;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import nearenough.protocol.RtEd25519;
|
||||
import nearenough.protocol.RtMessage;
|
||||
import nearenough.protocol.RtTag;
|
||||
import nearenough.server.clock.TestingClock;
|
||||
import nearenough.util.BytesUtil;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.ExpectedException;
|
||||
|
||||
public final class LongTermKeyTest {
|
||||
|
||||
@Rule
|
||||
public ExpectedException thrown = ExpectedException.none();
|
||||
|
||||
private static final byte[] SEED = new byte[MIN_SEED_LENGTH];
|
||||
|
||||
static {
|
||||
Arrays.fill(SEED, (byte) 'a');
|
||||
}
|
||||
|
||||
@Test
|
||||
public void successfulLongTermSignatureRoundTrip() throws Exception {
|
||||
LongTermKey ltk = new LongTermKey(SEED);
|
||||
RtEd25519.Verifier verifier = new RtEd25519.Verifier(ltk.longTermPublicKey());
|
||||
|
||||
byte[] content = "This is a test message".getBytes();
|
||||
|
||||
byte[] signature = ltk.signLongTermKey(content);
|
||||
verifier.update(content);
|
||||
|
||||
assertTrue(verifier.verify(signature));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void successfulDelegatedSignatureRoundTrip() throws Exception {
|
||||
LongTermKey ltk = new LongTermKey(SEED);
|
||||
|
||||
for (int i = 0; i < 10; i++) {
|
||||
ltk.newDelegatedKey();
|
||||
|
||||
byte[] content = (i + " bottles of beer").getBytes();
|
||||
|
||||
RtEd25519.Verifier verifier = new RtEd25519.Verifier(ltk.delegatedPublicKey());
|
||||
|
||||
byte[] signature = ltk.signDelegatedKey(content);
|
||||
verifier.update(content);
|
||||
|
||||
assertTrue(verifier.verify(signature));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void signingPriorToDelegationBoundsThrows() throws Exception {
|
||||
TestingClock clock = new TestingClock();
|
||||
LongTermKey key = new LongTermKey(SEED, Duration.ofDays(10), clock);
|
||||
|
||||
key.newDelegatedKey();
|
||||
|
||||
clock.decrement(5, TimeUnit.MINUTES);
|
||||
|
||||
assertThat(clock.now(), lessThan(key.delegationStart()));
|
||||
|
||||
thrown.expect(IllegalStateException.class);
|
||||
thrown.expectMessage("outside delegated key bounds");
|
||||
key.signDelegatedKey(new byte[1]);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void signingAfterDelegationBoundsThrows() throws Exception {
|
||||
TestingClock clock = new TestingClock();
|
||||
LongTermKey key = new LongTermKey(SEED, Duration.ofDays(10), clock);
|
||||
|
||||
key.newDelegatedKey();
|
||||
|
||||
clock.advance(15, TimeUnit.DAYS);
|
||||
|
||||
assertThat(clock.now(), greaterThan(key.delegationStart()));
|
||||
|
||||
thrown.expect(IllegalStateException.class);
|
||||
thrown.expectMessage("outside delegated key bounds");
|
||||
key.signDelegatedKey(new byte[1]);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void certMessageIsValid() throws Exception {
|
||||
TestingClock clock = new TestingClock();
|
||||
LongTermKey key = new LongTermKey(SEED, Duration.ofDays(1), clock);
|
||||
|
||||
RtMessage cert = key.asCertMessage();
|
||||
assertThat(cert.numTags(), equalTo(2));
|
||||
|
||||
// extract the DELE message
|
||||
RtMessage dele = RtMessage.fromBytes(cert.get(RtTag.DELE));
|
||||
assertThat(dele.numTags(), equalTo(3));
|
||||
|
||||
// PUBK
|
||||
assertArrayEquals(dele.get(RtTag.PUBK), key.delegatedPublicKey());
|
||||
|
||||
// MINT
|
||||
long minT = BytesUtil.getLongLE(dele.get(RtTag.MINT), 0);
|
||||
assertThat(minT, equalTo(key.delegationStart()));
|
||||
|
||||
// MAXT
|
||||
long maxT = BytesUtil.getLongLE(dele.get(RtTag.MAXT), 0);
|
||||
assertThat(maxT, equalTo(key.delegationEnd()));
|
||||
|
||||
// SIG on DELE
|
||||
RtEd25519.Verifier longTermVerify = new RtEd25519.Verifier(key.longTermPublicKey());
|
||||
longTermVerify.update(cert.get(RtTag.DELE));
|
||||
assertTrue(longTermVerify.verify(cert.get(RtTag.SIG)));
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user