blob: 51374c1621b462e6808358d10d2c96d4d24426a2 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 org.apache.tuweni.devp2p.v5.internal
import org.apache.tuweni.bytes.Bytes
import org.apache.tuweni.crypto.Hash
import org.apache.tuweni.crypto.SECP256K1
import org.apache.tuweni.devp2p.v5.AuthenticationProvider
import org.apache.tuweni.devp2p.v5.PacketCodec
import org.apache.tuweni.devp2p.v5.storage.RoutingTable
import org.apache.tuweni.devp2p.v5.encrypt.AES128GCM
import org.apache.tuweni.devp2p.v5.packet.FindNodeMessage
import org.apache.tuweni.devp2p.v5.packet.RandomMessage
import org.apache.tuweni.devp2p.v5.packet.UdpMessage
import org.apache.tuweni.devp2p.v5.packet.WhoAreYouMessage
import org.apache.tuweni.devp2p.v5.misc.AuthHeader
import org.apache.tuweni.devp2p.v5.misc.DecodeResult
import org.apache.tuweni.devp2p.v5.misc.EncodeResult
import org.apache.tuweni.devp2p.v5.misc.HandshakeInitParameters
import org.apache.tuweni.devp2p.v5.packet.NodesMessage
import org.apache.tuweni.devp2p.v5.packet.PingMessage
import org.apache.tuweni.devp2p.v5.packet.PongMessage
import org.apache.tuweni.devp2p.v5.packet.RegConfirmationMessage
import org.apache.tuweni.devp2p.v5.packet.RegTopicMessage
import org.apache.tuweni.devp2p.v5.packet.ReqTicketMessage
import org.apache.tuweni.devp2p.v5.packet.TicketMessage
import org.apache.tuweni.devp2p.v5.packet.TopicQueryMessage
import org.apache.tuweni.rlp.RLP
import org.apache.tuweni.rlp.RLPReader
import kotlin.IllegalArgumentException
class DefaultPacketCodec(
private val keyPair: SECP256K1.KeyPair,
private val routingTable: RoutingTable,
private val nodeId: Bytes = Hash.sha2_256(routingTable.getSelfEnr()),
private val authenticationProvider: AuthenticationProvider = DefaultAuthenticationProvider(keyPair, routingTable)
) : PacketCodec {
override fun encode(message: UdpMessage, destNodeId: Bytes, handshakeParams: HandshakeInitParameters?): EncodeResult {
if (message is WhoAreYouMessage) {
val magic = UdpMessage.magic(nodeId)
val content = message.encode()
return EncodeResult(magic, Bytes.wrap(magic, content))
}
val tag = UdpMessage.tag(nodeId, destNodeId)
if (message is RandomMessage) {
return encodeRandomMessage(tag, message)
}
val sessionKey = authenticationProvider.findSessionKey(destNodeId.toHexString())
val authHeader = handshakeParams?.let {
if (null == sessionKey) {
authenticationProvider.authenticate(handshakeParams)
} else null
}
val initiatorKey = authenticationProvider.findSessionKey(destNodeId.toHexString())?.initiatorKey
?: return encodeRandomMessage(tag, RandomMessage())
val messagePlain = Bytes.wrap(message.getMessageType(), message.encode())
return if (null != authHeader) {
val encodedHeader = authHeader.asRlp()
val authTag = authHeader.authTag
val encryptionMeta = Bytes.wrap(tag, encodedHeader)
val encryptionResult = AES128GCM.encrypt(initiatorKey, authTag, messagePlain, encryptionMeta)
if (message is NodesMessage) {
println(encryptionResult)
}
EncodeResult(authTag, Bytes.wrap(tag, encodedHeader, encryptionResult))
} else {
val authTag = UdpMessage.authTag()
val authTagHeader = RLP.encodeValue(authTag)
val encryptionResult = AES128GCM.encrypt(initiatorKey, authTag, messagePlain, tag)
EncodeResult(authTag, Bytes.wrap(tag, authTagHeader, encryptionResult))
}
}
override fun decode(message: Bytes): DecodeResult {
val tag = message.slice(0, UdpMessage.TAG_LENGTH)
val senderNodeId = UdpMessage.getSourceFromTag(tag, nodeId)
val contentWithHeader = message.slice(UdpMessage.TAG_LENGTH)
val decodedMessage = RLP.decode(contentWithHeader) { reader -> read(tag, senderNodeId, contentWithHeader, reader) }
return DecodeResult(senderNodeId, decodedMessage)
}
private fun read(tag: Bytes, senderNodeId: Bytes, contentWithHeader: Bytes, reader: RLPReader): UdpMessage {
// Distinguish auth header or auth tag
var authHeader: AuthHeader? = null
var authTag: Bytes = Bytes.EMPTY
if (reader.nextIsList()) {
if (WHO_ARE_YOU_MESSAGE_LENGTH == contentWithHeader.size()) {
return WhoAreYouMessage.create(contentWithHeader)
}
authHeader = reader.readList { listReader ->
val authenticationTag = listReader.readValue()
val idNonce = listReader.readValue()
val authScheme = listReader.readString()
val ephemeralPublicKey = listReader.readValue()
val authResponse = listReader.readValue()
return@readList AuthHeader(authenticationTag, idNonce, ephemeralPublicKey, authResponse, authScheme)
}
authenticationProvider.finalizeHandshake(senderNodeId, authHeader)
} else {
authTag = reader.readValue()
}
val encryptedContent = contentWithHeader.slice(reader.position())
// Decrypt
val decryptionKey = authenticationProvider.findSessionKey(senderNodeId.toHexString())?.initiatorKey
?: return RandomMessage.create(authTag, encryptedContent)
val decryptMetadata = authHeader?.let { Bytes.wrap(tag, authHeader.asRlp()) } ?: tag
val decryptedContent = AES128GCM.decrypt(encryptedContent, decryptionKey, decryptMetadata)
val messageType = decryptedContent.slice(0, Byte.SIZE_BYTES)
val message = decryptedContent.slice(Byte.SIZE_BYTES)
// Retrieve result
return when (messageType.toInt()) {
1 -> PingMessage.create(message)
2 -> PongMessage.create(message)
3 -> FindNodeMessage.create(message)
4 -> NodesMessage.create(message)
5 -> ReqTicketMessage.create(message)
6 -> TicketMessage.create(message)
7 -> RegTopicMessage.create(message)
8 -> RegConfirmationMessage.create(message)
9 -> TopicQueryMessage.create(message)
else -> throw IllegalArgumentException("Unknown message retrieved")
}
}
private fun encodeRandomMessage(tag: Bytes, message: RandomMessage): EncodeResult {
val rlpAuthTag = RLP.encodeValue(message.authTag)
val content = message.encode()
return EncodeResult(message.authTag, Bytes.wrap(tag, rlpAuthTag, content))
}
companion object {
private const val WHO_ARE_YOU_MESSAGE_LENGTH = 48
}
}