diff --git a/core/src/main/groovy/com/muwire/core/Constants.groovy b/core/src/main/groovy/com/muwire/core/Constants.groovy new file mode 100644 index 00000000..de3193a7 --- /dev/null +++ b/core/src/main/groovy/com/muwire/core/Constants.groovy @@ -0,0 +1,8 @@ +package com.muwire.core + +import net.i2p.crypto.SigType + +class Constants { + public static final byte PERSONA_VERSION = (byte)1 + public static final SigType SIG_TYPE = SigType.ECDSA_SHA512_P521 // TODO: decide which +} diff --git a/core/src/main/groovy/com/muwire/core/Core.groovy b/core/src/main/groovy/com/muwire/core/Core.groovy index 5d159b71..68387b14 100644 --- a/core/src/main/groovy/com/muwire/core/Core.groovy +++ b/core/src/main/groovy/com/muwire/core/Core.groovy @@ -1,5 +1,7 @@ package com.muwire.core +import java.nio.charset.StandardCharsets + import com.muwire.core.connection.ConnectionAcceptor import com.muwire.core.connection.ConnectionEstablisher import com.muwire.core.connection.ConnectionEvent @@ -21,6 +23,12 @@ import net.i2p.client.I2PSession import net.i2p.client.streaming.I2PSocketManager import net.i2p.client.streaming.I2PSocketManagerFactory import net.i2p.client.streaming.I2PSocketOptions +import net.i2p.crypto.DSAEngine +import net.i2p.crypto.SigType +import net.i2p.data.Destination +import net.i2p.data.PrivateKey +import net.i2p.data.Signature +import net.i2p.data.SigningPrivateKey @Log class Core { @@ -46,14 +54,13 @@ class Core { props = new MuWireSettings(props) - log.info("initializing I2P socket manager") def i2pClient = new I2PClientFactory().createClient() File keyDat = new File(home, "key.dat") if (!keyDat.exists()) { log.info("Creating new key.dat") keyDat.withOutputStream { - i2pClient.createDestination(it) + i2pClient.createDestination(it, Constants.SIG_TYPE) } } @@ -68,7 +75,32 @@ class Core { socketManager.getDefaultOptions().setConnectTimeout(30000) i2pSession = socketManager.getSession() - + Persona me + def destination = new Destination() + def spk = new SigningPrivateKey(Constants.SIG_TYPE) + keyDat.withInputStream { + destination.readBytes(it) + def privateKey = new PrivateKey() + privateKey.readBytes(it) + spk.readBytes(it) + } + + def baos = new ByteArrayOutputStream() + def daos = new DataOutputStream(baos) + daos.write(Constants.PERSONA_VERSION) + daos.writeShort((short)props.getNickname().length()) + daos.write(props.getNickname().getBytes(StandardCharsets.UTF_8)) + destination.writeBytes(daos) + daos.flush() + byte [] payload = baos.toByteArray() + Signature sig = DSAEngine.getInstance().sign(payload, spk) + + baos = new ByteArrayOutputStream() + baos.write(payload) + sig.writeBytes(baos) + me = new Persona(new ByteArrayInputStream(baos.toByteArray())) + log.info("Loaded myself as "+me.getHumanReadableName()) + EventBus eventBus = new EventBus() log.info("initializing trust service") diff --git a/core/src/main/groovy/com/muwire/core/InvalidSignatureException.groovy b/core/src/main/groovy/com/muwire/core/InvalidSignatureException.groovy new file mode 100644 index 00000000..ecbe9d45 --- /dev/null +++ b/core/src/main/groovy/com/muwire/core/InvalidSignatureException.groovy @@ -0,0 +1,17 @@ +package com.muwire.core + +class InvalidSignatureException extends Exception { + + public InvalidSignatureException(String message, Throwable cause) { + super(message, cause); + } + + public InvalidSignatureException(String message) { + super(message); + } + + public InvalidSignatureException(Throwable cause) { + super(cause); + } + +} diff --git a/core/src/main/groovy/com/muwire/core/MuWireSettings.groovy b/core/src/main/groovy/com/muwire/core/MuWireSettings.groovy index 7dbe44aa..bb917e6a 100644 --- a/core/src/main/groovy/com/muwire/core/MuWireSettings.groovy +++ b/core/src/main/groovy/com/muwire/core/MuWireSettings.groovy @@ -10,7 +10,7 @@ class MuWireSettings { isLeaf = Boolean.valueOf(props.get("leaf","false")) allowUntrusted = Boolean.valueOf(props.get("allowUntrusted","true")) crawlerResponse = CrawlerResponse.valueOf(props.get("crawlerResponse","REGISTERED")) - nickname = props.getProperty("nickname") + nickname = props.getProperty("nickname","MuWireUser") } final boolean isLeaf @@ -37,4 +37,8 @@ class MuWireSettings { void setCrawlerResponse(CrawlerResponse crawlerResponse) { this.crawlerResponse = crawlerResponse } + + String getNickname() { + nickname + } } diff --git a/core/src/main/groovy/com/muwire/core/Name.groovy b/core/src/main/groovy/com/muwire/core/Name.groovy index 126628c4..4c9ac294 100644 --- a/core/src/main/groovy/com/muwire/core/Name.groovy +++ b/core/src/main/groovy/com/muwire/core/Name.groovy @@ -23,7 +23,7 @@ public class Name { public void write(OutputStream out) throws IOException { DataOutputStream dos = new DataOutputStream(out) dos.writeShort(name.length()) - dos.writeBuffer(name.getBytes(StandardCharsets.UTF_8)) + dos.write(name.getBytes(StandardCharsets.UTF_8)) } public getName() { diff --git a/core/src/main/groovy/com/muwire/core/Persona.groovy b/core/src/main/groovy/com/muwire/core/Persona.groovy new file mode 100644 index 00000000..5b38d24b --- /dev/null +++ b/core/src/main/groovy/com/muwire/core/Persona.groovy @@ -0,0 +1,74 @@ +package com.muwire.core + +import net.i2p.crypto.DSAEngine +import net.i2p.crypto.SigType +import net.i2p.data.Destination +import net.i2p.data.Signature +import net.i2p.data.SigningPublicKey + +public class Persona { + private static final int SIG_LEN = Constants.SIG_TYPE.getSigLen() + + private final byte version + private final Name name + private final Destination destination + private final byte[] sig + private volatile String humanReadableName + private volatile byte[] payload + + public Persona(InputStream personaStream) throws IOException, InvalidSignatureException { + version = (byte) (personaStream.read() & 0xFF) + if (version != Constants.PERSONA_VERSION) + throw new IOException("Unknown version "+version) + + name = new Name(personaStream) + destination = Destination.create(personaStream) + sig = new byte[SIG_LEN] + DataInputStream dis = new DataInputStream(personaStream) + dis.readFully(sig) + if (!verify(version, name, destination, sig)) + throw new InvalidSignatureException(getHumanReadableName() + " didn't verify") + } + + private static boolean verify(byte version, Name name, Destination destination, byte [] sig) { + ByteArrayOutputStream baos = new ByteArrayOutputStream() + baos.write(version) + name.write(baos) + destination.writeBytes(baos) + byte[] payload = baos.toByteArray() + SigningPublicKey spk = destination.getSigningPublicKey() + Signature signature = new Signature(Constants.SIG_TYPE, sig) + DSAEngine.getInstance().verifySignature(signature, payload, spk) + } + + public void write(OutputStream out) throws IOException { + if (payload == null) { + ByteArrayOutputStream baos = new ByteArrayOutputStream() + baos.write(version) + name.write(baos) + destination.writeBytes(baos) + baos.write(sig) + payload = baos.toByteArray() + } + out.write(payload) + } + + public String getHumanReadableName() { + if (humanReadableName == null) + humanReadableName = name.getName() + "@" + destination.toBase32().substring(0,32) + humanReadableName + } + + @Override + public int hashCode() { + name.hashCode() ^ destination.hashCode() + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof Persona)) + return false + Persona other = (Persona)o + name.equals(other.name) && destination.equals(other.destination) + } +}