Update to reflect client-only status.

This commit is contained in:
Stuart Stock
2017-02-10 10:00:06 -06:00
parent 31f48cf91a
commit 523ff4a2d2
7 changed files with 10 additions and 440 deletions

View File

@ -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

View File

@ -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.

View File

@ -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();
}
}

View File

@ -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();
}

View File

@ -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();
}
}

View File

@ -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);
}
}

View File

@ -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)));
}
}