All Downloads are FREE. Search and download functionalities are using the official Maven repository.

sss.openstar.message.payloads.MessageEcryption.scala Maven / Gradle / Ivy

package sss.openstar.message.payloads

import java.util
import akka.util.ByteString
import scorex.crypto.signatures.SharedSecret
import sss.ancillary.FailWithException.fail
import sss.ancillary.{Guid, Logging}
import sss.openstar.account.{NodeIdTag, NodeIdentity, NodeVerifier}
import sss.openstar.balanceledger._
import sss.openstar.crypto.AESDetails.AESEncodedKey
import sss.openstar.crypto.CBCEncryption.InitVector
import sss.openstar.crypto.{AESDetails, CBCEncryption, SeedBytes}
import sss.openstar.identityledger.IdentityServiceQuery
import sss.openstar.message.payloads.ThreadedMessage.{Threadable, Threaded}
import sss.openstar.message.{BodyToBeEncryptedFromBytes, MessageComposite, MessageUpdater}
import sss.openstar.util.ThrowUtil.throwRuntime

import scala.concurrent.{ExecutionContext, Future}
import scala.util.{Failure, Success, Try}

/**
  * Created by alan on 6/28/16.
  */
object MessageEcryption {


  case class EmbeddedBounty(

                             amountOfBounty: Long,
                             bountyReturnBlockHeight: Long,
                             addrOfBounty: TxIndex,
                           )

  case class EncryptedMessage(
                               bountyOpt: Option[EmbeddedBounty],
                               author: NodeIdTag,
                               receivers: Seq[EncryptedSessionKey],
                               encrypted: ByteString,
                               iv: InitVector,
                               guid: Guid,
                               childGuids: Seq[Guid]
                             ) extends MessageComposite
    with Threadable
    with Threaded
    with UtxoWatcher
    with Logging {


    override def apply[C >: MessageComposite](m: MessageUpdater): C = m match {

      case t: Threadable if !childGuids.contains(t.guid) =>
        this.copy(childGuids = childGuids :+ t.guid)

      case _: Threadable =>
        this

      case x =>
        log.warn(s"Cannot add $x to Encrypted Msg")
        this
    }


    override def watch: Seq[TxIndex] = {
      bountyOpt.map(bounty =>
      receivers
        .indices
        .map(
          i => bounty.addrOfBounty.copy(index = bounty.addrOfBounty.index + i)
        )
      ).getOrElse(Seq.empty)
    }

    def decrypt(receiver: NodeIdentity, lookup: NodeIdTag => NodeVerifier)(implicit ec: ExecutionContext): Future[BodyToBeEncrypted] = {

      val (receiverData, sendPublicKeyAccount) = (if (receiver.id == author.nodeId) {
        receivers.find(_.identity.nodeId != author.nodeId) map { found =>
          val verifier = lookup(found.identity)
          (found, verifier)
        }
      } else {
        receivers.find(_.identity.nodeId == receiver.id) map { found =>
          (found, lookup(author))
        }
      }).getOrElse(
        fail(s"Receiver ${receiver.id} is not in the recipient list")
      )

      val sharedSecretF: Future[SharedSecret] = receiver
        .defaultNodeVerifier
        .signer
        .createSharedSecret(sendPublicKeyAccount.verifier.typedPublicKey)

      sharedSecretF.map { sharedSecret =>
        val decryptedSessionKey = Try(CBCEncryption.decrypt(
          AESDetails.aesKeyFromByteArray(sharedSecret),
          receiverData.encryptedSessionKey.toArray,
          CBCEncryption.initVector(receiverData.iv.toArray)
        )) match {
          case Success(s) => AESEncodedKey(s)
          case Failure(exception) =>
            log.warn(s"REC ${receiver.id} author $author recdata ${receiverData.identity}")
            throw exception
        }

        val decryptedMessage = CBCEncryption.decrypt(
          decryptedSessionKey,
          encrypted.toArray,
          iv
        )

        decryptedMessage.toBodyToBeEncrypted
      }

    }
  }

  case class AttachmentDetails(name: String, mimeType: String, size: Long)

  case class MessageAttachments(attachmentProvider: String, attachmentDetails: Seq[AttachmentDetails] = List.empty,
                                key: AESEncodedKey = AESDetails.generateAESKey())


  case class EncryptedSessionKey(
                                  identity: NodeIdTag,
                                  iv: ByteString,
                                  encryptedSessionKey: ByteString) {
    require(iv.size >= 16, s"128 bit key minimum size, ${iv.size} not allowed")
  }

  case class BodyToBeEncrypted(
                                text: String,
                                secret: Array[Byte],
                                attachmentsOpt: Option[MessageAttachments]
                              )


  def encryptWithEmbeddedSecret(
                                 amountOfBounty: Long,
                                 bountyReturnBlockHeight: Long,
                                 addrOfBounty: TxIndex,
                                 sender: NodeIdentity,
                                 receivers: Seq[NodeVerifier],
                                 text: String,
                                 attachments: Option[MessageAttachments],
                                 secret: Array[Byte],
                                 guid: Guid,
                                 guids: Seq[Guid]
                               )(implicit ec: ExecutionContext): Future[EncryptedMessage] =
    encryptWithEmbeddedSecret(
      Some(EmbeddedBounty(amountOfBounty, bountyReturnBlockHeight, addrOfBounty)),
      sender,
      receivers,
      text,
      attachments,
      secret,
      guid,
      guids
    )

  def encryptWithEmbeddedSecret(
                                 bountyOpt: Option[EmbeddedBounty],
                                 sender: NodeIdentity,
                                 receivers: Seq[NodeVerifier],
                                 text: String,
                                 attachments: Option[MessageAttachments],
                                 secret: Array[Byte],
                                 guid: Guid,
                                 guids: Seq[Guid]
                               )(implicit ec: ExecutionContext): Future[EncryptedMessage] = {

    val sessionKey = AESDetails.generateAESKey()

    val receiversEncryptedSessionKeys = receivers map { receiver =>
      val signer = sender.defaultNodeVerifier.signer
      val sharedSecretF: Future[SharedSecret] = signer.createSharedSecret(receiver.verifier.typedPublicKey)
      val iv = CBCEncryption.newInitVector(SeedBytes)
      sharedSecretF.map { sharedSecret =>
        val aesSessionKey = AESDetails.aesKeyFromByteArray(sharedSecret)
        EncryptedSessionKey(receiver.nodeIdTag,
          ByteString(iv.bytes),
          ByteString(CBCEncryption.encrypt(aesSessionKey, sessionKey.value, iv))
        )
      }

    }

    val seqEncSessKeysF: Future[Seq[EncryptedSessionKey]] = Future.sequence(receiversEncryptedSessionKeys)

    val iv = CBCEncryption.newInitVector(SeedBytes)
    val bytes: Array[Byte] = BodyToBeEncrypted(text, secret, attachments).toBytes
    val encryptedMessage = CBCEncryption.encrypt(sessionKey, bytes, iv)

    seqEncSessKeysF.map { receiversEncryptedSessionKeys =>
      EncryptedMessage(
        bountyOpt,
        NodeIdTag(sender.id, sender.tag),
        receiversEncryptedSessionKeys,
        ByteString(encryptedMessage),
        iv,
        guid,
        guids
      )
    }

  }

  def createLookupId(identityServiceQuery: IdentityServiceQuery): NodeIdTag => NodeVerifier =
    nId =>
      identityServiceQuery
        .toVerifier(
          identityServiceQuery
            .accountOpt(nId.nodeId, nId.tag)
            .getOrElse(fail(s"No such user ${nId.nodeId}")
            )
        )


}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy