diff --git a/build.gradle b/build.gradle index df0b9d4..33583e4 100644 --- a/build.gradle +++ b/build.gradle @@ -1,5 +1,5 @@ buildscript { - ext.kotlin_version = '1.1.3' + ext.kotlin_version = '1.1.4-2' repositories { mavenCentral() } @@ -28,6 +28,7 @@ subprojects { repositories { mavenCentral() maven { url 'https://oss.sonatype.org/content/repositories/snapshots/' } + jcenter() } dependencies { compile "org.jetbrains.kotlin:kotlin-stdlib-jre7" @@ -142,6 +143,7 @@ subprojects { dependency 'com.madgag.spongycastle:prov:1.56.0.0' dependency 'org.apache.commons:commons-lang3:3.6' dependency 'org.flywaydb:flyway-core:4.2.0' + dependency 'com.beust:klaxon:0.31' dependency 'args4j:args4j:2.33' dependency 'org.ini4j:ini4j:0.5.4' diff --git a/core/src/main/kotlin/ch/dissem/bitmessage/DefaultMessageListener.kt b/core/src/main/kotlin/ch/dissem/bitmessage/DefaultMessageListener.kt index ac30587..ad32bf9 100644 --- a/core/src/main/kotlin/ch/dissem/bitmessage/DefaultMessageListener.kt +++ b/core/src/main/kotlin/ch/dissem/bitmessage/DefaultMessageListener.kt @@ -23,6 +23,7 @@ import ch.dissem.bitmessage.entity.Plaintext.Status.PUBKEY_REQUESTED import ch.dissem.bitmessage.entity.payload.* import ch.dissem.bitmessage.entity.valueobject.InventoryVector import ch.dissem.bitmessage.exception.DecryptionFailedException +import ch.dissem.bitmessage.ports.AlreadyStoredException import ch.dissem.bitmessage.ports.Labeler import ch.dissem.bitmessage.ports.NetworkHandler import ch.dissem.bitmessage.utils.Strings.hex @@ -65,7 +66,7 @@ open class DefaultMessageListener( protected fun receive(objectMessage: ObjectMessage, getPubkey: GetPubkey) { val identity = ctx.addressRepository.findIdentity(getPubkey.ripeTag) - if (identity != null && identity.privateKey != null && !identity.isChan) { + if (identity?.privateKey != null && !identity.isChan) { LOG.info("Got pubkey request for identity " + identity) // FIXME: only send pubkey if it wasn't sent in the last TTL.pubkey() days ctx.sendPubkey(identity, objectMessage.stream) @@ -90,7 +91,6 @@ open class DefaultMessageListener( } } catch (_: DecryptionFailedException) { } - } private fun updatePubkey(address: BitmessageAddress, pubkey: Pubkey) { @@ -157,14 +157,18 @@ open class DefaultMessageListener( msg.inventoryVector = iv labeler.setLabels(msg) - ctx.messageRepository.save(msg) - listener.receive(msg) + try { + ctx.messageRepository.save(msg) + listener.receive(msg) - if (msg.type == Plaintext.Type.MSG && msg.to!!.has(Pubkey.Feature.DOES_ACK)) { - msg.ackMessage?.let { - ctx.inventory.storeObject(it) - ctx.networkHandler.offer(it.inventoryVector) - } ?: LOG.debug("ack message expected") + if (msg.type == Plaintext.Type.MSG && msg.to!!.has(Pubkey.Feature.DOES_ACK)) { + msg.ackMessage?.let { + ctx.inventory.storeObject(it) + ctx.networkHandler.offer(it.inventoryVector) + } ?: LOG.debug("ack message expected") + } + } catch (e: AlreadyStoredException) { + LOG.trace("Message was already received before.", e) } } diff --git a/core/src/main/kotlin/ch/dissem/bitmessage/entity/Plaintext.kt b/core/src/main/kotlin/ch/dissem/bitmessage/entity/Plaintext.kt index 3f0f551..aa28f6e 100644 --- a/core/src/main/kotlin/ch/dissem/bitmessage/entity/Plaintext.kt +++ b/core/src/main/kotlin/ch/dissem/bitmessage/entity/Plaintext.kt @@ -229,7 +229,8 @@ class Plaintext private constructor( ) constructor(builder: Builder) : this( - type = builder.type, + // Calling prepare() here is somewhat ugly, but also a foolproof way to make sure the builder is properly initialized + type = builder.prepare().type, from = builder.from ?: throw IllegalStateException("sender identity not set"), to = builder.to, encodingCode = builder.encoding, @@ -438,7 +439,7 @@ class Plaintext private constructor( if (this === other) return true if (other !is Plaintext) return false return encoding == other.encoding && - from == other.from && + from.address == other.from.address && Arrays.equals(message, other.message) && ackMessage == other.ackMessage && Arrays.equals(to?.ripe, other.to?.ripe) && @@ -532,6 +533,7 @@ class Plaintext private constructor( private var nonceTrialsPerByte: Long = 0 private var extraBytes: Long = 0 private var destinationRipe: ByteArray? = null + private var preventAck: Boolean = false internal var encoding: Long = 0 internal var message = ByteArray(0) internal var ackData: ByteArray? = null @@ -611,6 +613,12 @@ class Plaintext private constructor( return this } + @JvmOverloads + fun preventAck(preventAck: Boolean = true): Builder { + this.preventAck = preventAck + return this + } + fun encoding(encoding: Encoding): Builder { this.encoding = encoding.code return this @@ -655,7 +663,7 @@ class Plaintext private constructor( return this } - fun signature(signature: ByteArray): Builder { + fun signature(signature: ByteArray?): Builder { this.signature = signature return this } @@ -700,7 +708,7 @@ class Plaintext private constructor( return this } - fun build(): Plaintext { + internal fun prepare(): Builder { if (from == null) { from = BitmessageAddress(Factory.createPubkey( addressVersion, @@ -715,12 +723,19 @@ class Plaintext private constructor( if (to == null && type != Type.BROADCAST && destinationRipe != null) { to = BitmessageAddress(0, 0, destinationRipe!!) } - if (type == MSG && ackMessage == null && ackData == null) { + if (preventAck) { + ackData = null + ackMessage = null + } else if (type == MSG && ackMessage == null && ackData == null) { ackData = cryptography().randomBytes(Msg.ACK_LENGTH) } if (ttl <= 0) { ttl = TTL.msg } + return this + } + + fun build(): Plaintext { return Plaintext(this) } } diff --git a/core/src/main/kotlin/ch/dissem/bitmessage/entity/valueobject/Label.kt b/core/src/main/kotlin/ch/dissem/bitmessage/entity/valueobject/Label.kt index 6fd4fd6..3bba8d6 100644 --- a/core/src/main/kotlin/ch/dissem/bitmessage/entity/valueobject/Label.kt +++ b/core/src/main/kotlin/ch/dissem/bitmessage/entity/valueobject/Label.kt @@ -25,7 +25,8 @@ data class Label( /** * RGBA representation for the color. */ - var color: Int = 0 + var color: Int = 0, + var ord: Int = 1000 ) : Serializable { var id: Any? = null diff --git a/core/src/main/kotlin/ch/dissem/bitmessage/entity/valueobject/PrivateKey.kt b/core/src/main/kotlin/ch/dissem/bitmessage/entity/valueobject/PrivateKey.kt index cfc618a..c7f0b54 100644 --- a/core/src/main/kotlin/ch/dissem/bitmessage/entity/valueobject/PrivateKey.kt +++ b/core/src/main/kotlin/ch/dissem/bitmessage/entity/valueobject/PrivateKey.kt @@ -34,48 +34,37 @@ import java.util.* * Represents a private key. Additional information (stream, version, features, ...) is stored in the accompanying * [Pubkey] object. */ -class PrivateKey : Streamable { - - val privateSigningKey: ByteArray - val privateEncryptionKey: ByteArray +data class PrivateKey( + val privateSigningKey: ByteArray, + val privateEncryptionKey: ByteArray, val pubkey: Pubkey +) : Streamable { - constructor(shorter: Boolean, stream: Long, nonceTrialsPerByte: Long, extraBytes: Long, vararg features: Pubkey.Feature) { - var privSK: ByteArray - var pubSK: ByteArray - var privEK: ByteArray - var pubEK: ByteArray - var ripe: ByteArray - do { - privSK = cryptography().randomBytes(PRIVATE_KEY_SIZE) - privEK = cryptography().randomBytes(PRIVATE_KEY_SIZE) - pubSK = cryptography().createPublicKey(privSK) - pubEK = cryptography().createPublicKey(privEK) - ripe = Pubkey.getRipe(pubSK, pubEK) - } while (ripe[0].toInt() != 0 || shorter && ripe[1].toInt() != 0) - this.privateSigningKey = privSK - this.privateEncryptionKey = privEK - this.pubkey = cryptography().createPubkey(Pubkey.LATEST_VERSION, stream, privateSigningKey, privateEncryptionKey, - nonceTrialsPerByte, extraBytes, *features) - } - - constructor(privateSigningKey: ByteArray, privateEncryptionKey: ByteArray, pubkey: Pubkey) { - this.privateSigningKey = privateSigningKey - this.privateEncryptionKey = privateEncryptionKey - this.pubkey = pubkey - } + constructor( + shorter: Boolean, + stream: Long, + nonceTrialsPerByte: Long, extraBytes: Long, + vararg features: Pubkey.Feature + ) : this( + Builder(version = Pubkey.LATEST_VERSION, stream = stream, shorter = shorter) + .random() + .nonceTrialsPerByte(nonceTrialsPerByte) + .extraBytes(extraBytes) + .features(features) + .generate()) constructor(address: BitmessageAddress, passphrase: String) : this(address.version, address.stream, passphrase) - constructor(version: Long, stream: Long, passphrase: String) : this(Builder(version, stream, false).seed(passphrase).generate()) + constructor(version: Long, stream: Long, passphrase: String) : this( + Builder(version, stream, false).seed(passphrase).generate() + ) - private constructor(builder: Builder) { - this.privateSigningKey = builder.privSK!! - this.privateEncryptionKey = builder.privEK!! - this.pubkey = Factory.createPubkey(builder.version, builder.stream, builder.pubSK!!, builder.pubEK!!, - InternalContext.NETWORK_NONCE_TRIALS_PER_BYTE, InternalContext.NETWORK_EXTRA_BYTES) - } + private constructor(builder: Builder) : this( + builder.privSK!!, builder.privEK!!, + Factory.createPubkey(builder.version, builder.stream, builder.pubSK!!, builder.pubEK!!, + builder.nonceTrialsPerByte, builder.extraBytes, *builder.features) + ) private class Builder internal constructor(internal val version: Long, internal val stream: Long, internal val shorter: Boolean) { @@ -87,6 +76,30 @@ class PrivateKey : Streamable { internal var pubSK: ByteArray? = null internal var pubEK: ByteArray? = null + internal var nonceTrialsPerByte = InternalContext.NETWORK_NONCE_TRIALS_PER_BYTE + internal var extraBytes = InternalContext.NETWORK_EXTRA_BYTES + internal var features: Array = emptyArray() + + internal fun random(): Builder { + seed = cryptography().randomBytes(1024) + return this + } + + fun nonceTrialsPerByte(nonceTrialsPerByte: Long): Builder { + this.nonceTrialsPerByte = nonceTrialsPerByte + return this + } + + fun extraBytes(extraBytes: Long): Builder { + this.extraBytes = extraBytes + return this + } + + fun features(features: Array): Builder { + this.features = features + return this + } + internal fun seed(passphrase: String): Builder { try { seed = passphrase.toByteArray(charset("UTF-8")) @@ -143,6 +156,13 @@ class PrivateKey : Streamable { Encode.varBytes(privateEncryptionKey, buffer) } + override fun equals(other: Any?) = other is PrivateKey + && Arrays.equals(privateEncryptionKey, other.privateEncryptionKey) + && Arrays.equals(privateSigningKey, other.privateSigningKey) + && pubkey == other.pubkey + + override fun hashCode() = pubkey.hashCode() + companion object { @JvmField val PRIVATE_KEY_SIZE = 32 diff --git a/core/src/main/kotlin/ch/dissem/bitmessage/ports/AbstractCryptography.kt b/core/src/main/kotlin/ch/dissem/bitmessage/ports/AbstractCryptography.kt index f6b779b..5792f0f 100644 --- a/core/src/main/kotlin/ch/dissem/bitmessage/ports/AbstractCryptography.kt +++ b/core/src/main/kotlin/ch/dissem/bitmessage/ports/AbstractCryptography.kt @@ -198,10 +198,10 @@ abstract class AbstractCryptography protected constructor(@JvmField protected va } companion object { - protected val LOG = LoggerFactory.getLogger(Cryptography::class.java) + protected val LOG = LoggerFactory.getLogger(Cryptography::class.java)!! private val RANDOM = SecureRandom() private val TWO = BigInteger.valueOf(2) - private val TWO_POW_64 = TWO.pow(64) - private val TWO_POW_16 = TWO.pow(16) + private val TWO_POW_64 = TWO.pow(64)!! + private val TWO_POW_16 = TWO.pow(16)!! } } diff --git a/core/src/main/kotlin/ch/dissem/bitmessage/ports/AbstractMessageRepository.kt b/core/src/main/kotlin/ch/dissem/bitmessage/ports/AbstractMessageRepository.kt index b6d2108..d426dac 100644 --- a/core/src/main/kotlin/ch/dissem/bitmessage/ports/AbstractMessageRepository.kt +++ b/core/src/main/kotlin/ch/dissem/bitmessage/ports/AbstractMessageRepository.kt @@ -49,6 +49,8 @@ abstract class AbstractMessageRepository : MessageRepository, InternalContext.Co } } + override fun getAllMessages() = find("1=1") + override fun getMessage(id: Any): Plaintext { if (id is Long) { return single(find("id=" + id)) ?: throw IllegalArgumentException("There is no message with id $id") diff --git a/core/src/main/kotlin/ch/dissem/bitmessage/ports/AlreadyStoredException.kt b/core/src/main/kotlin/ch/dissem/bitmessage/ports/AlreadyStoredException.kt new file mode 100644 index 0000000..619f546 --- /dev/null +++ b/core/src/main/kotlin/ch/dissem/bitmessage/ports/AlreadyStoredException.kt @@ -0,0 +1,23 @@ +/* + * Copyright 2017 Christian Basler + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License 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 ch.dissem.bitmessage.ports + +/** + * Should be thrown if a received and decrypted message can't be stored because it has already been received and stored. + * (So it's not announced again to the client.) + */ +class AlreadyStoredException(message: String? = null, cause: Throwable? = null) : Exception(message, cause) diff --git a/core/src/main/kotlin/ch/dissem/bitmessage/ports/MessageRepository.kt b/core/src/main/kotlin/ch/dissem/bitmessage/ports/MessageRepository.kt index 51e0d02..68fac7a 100644 --- a/core/src/main/kotlin/ch/dissem/bitmessage/ports/MessageRepository.kt +++ b/core/src/main/kotlin/ch/dissem/bitmessage/ports/MessageRepository.kt @@ -28,8 +28,12 @@ interface MessageRepository { fun getLabels(vararg types: Label.Type): List