From 26b41e7b4d3c287ed7f75265176e8adeb3700364 Mon Sep 17 00:00:00 2001 From: Dmitry Kurinskiy Date: Thu, 1 Mar 2018 15:36:56 +0300 Subject: [PATCH] Cli demo (#66) * CLI reorganization WIP * clean up reference.conf, add application.conf to gitignore * linearizing ClientComposer; some free operations * keyPath config path corrected * compilation error fixed * AuthorizedClient removed * test compilation error fixed * fix keystore tests * Merge branch 'master' into cli-demo # Conflicts: # client/src/main/scala/fluence/client/ClientApp.scala # client/src/main/scala/fluence/client/ConfigParsers.scala # client/src/main/scala/fluence/client/FluenceClient.scala # client/src/main/scala/fluence/client/KademliaConfigParser.scala # client/src/main/scala/fluence/client/cli/CliOps.scala # client/src/main/scala/fluence/client/config/KademliaConfigParser.scala * cats effect 0.9 * replicationN in ClientApp * add logs to FileKeyStorage * add logs to node dataset storage * fix typos * add logs to server contract allocator * ClientApp recursive calls * launches and joins with docker * with volumes * update port parameters * update port parameters * fix test * update port parameters * add check before put * change assertion * no fluence_grpc_port * run client in docker * some TODOs for Cli * fix assertion * add some logs * add implicit --- .../scala/fluence/crypto/FileKeyStorage.scala | 46 ++++++++++++++++--- .../fluence/crypto/algorithm/AesCrypt.scala | 1 - .../test/scala/fluence/crypto/AesSpec.scala | 8 ++-- src/main/scala/fluence/crypto/KeyStore.scala | 30 +++++++----- .../scala/fluence/crypto/KeyStoreSpec.scala | 14 +++--- 5 files changed, 69 insertions(+), 30 deletions(-) diff --git a/jvm/src/main/scala/fluence/crypto/FileKeyStorage.scala b/jvm/src/main/scala/fluence/crypto/FileKeyStorage.scala index 1bc85ce..a929741 100644 --- a/jvm/src/main/scala/fluence/crypto/FileKeyStorage.scala +++ b/jvm/src/main/scala/fluence/crypto/FileKeyStorage.scala @@ -18,7 +18,7 @@ package fluence.crypto import java.io.File -import java.nio.file.Files +import java.nio.file.{ Files, Paths } import cats.MonadError import cats.syntax.flatMap._ @@ -29,21 +29,37 @@ import io.circe.syntax._ import scala.language.higherKinds -class FileKeyStorage[F[_]](file: File)(implicit F: MonadError[F, Throwable]) { +/** + * TODO use cats IO + * File based storage for crypto keys. + * + * @param file Path to keys in file system + */ +class FileKeyStorage[F[_]](file: File)(implicit F: MonadError[F, Throwable]) extends slogging.LazyLogging { import KeyStore._ + def readKeyPair: F[KeyPair] = { - val keyBytes = Files.readAllBytes(file.toPath) + val keyBytes = Files.readAllBytes(file.toPath) // TODO: it throws! for { storageOp ← F.fromEither(decode[Option[KeyStore]](new String(keyBytes))) storage ← storageOp match { - case None ⇒ F.raiseError[KeyStore](new RuntimeException("Cannot parse file with keys.")) - case Some(ks) ⇒ F.pure(ks) + case None ⇒ + logger.warn(s"Reading keys from file=$file was failed") + F.raiseError[KeyStore](new RuntimeException("Cannot parse file with keys.")) + case Some(ks) ⇒ + logger.info(s"Reading keys from file=$file was success") + F.pure(ks) } } yield storage.keyPair } def storeSecretKey(key: KeyPair): F[Unit] = F.catchNonFatal { + logger.info("Storing secret key to file: " + file) + if (!file.getParentFile.exists()) { + logger.info(s"Parent directory does not exist: ${file.getParentFile}, trying to create") + Files.createDirectories(file.getParentFile.toPath) + } if (!file.exists()) file.createNewFile() else throw new RuntimeException(file.getAbsolutePath + " already exists") val str = KeyStore(key).asJson.toString() @@ -57,6 +73,24 @@ class FileKeyStorage[F[_]](file: File)(implicit F: MonadError[F, Throwable]) { for { newKeys ← f _ ← storeSecretKey(newKeys) - } yield newKeys + } yield { + logger.info(s"New keys were generated and saved to file=$file") + newKeys + } } } + +object FileKeyStorage { + /** + * Generates or loads keypair + * + * @param keyPath Path to store keys in + * @param algo Sign algo + * @return Keypair, either loaded or freshly generated + */ + def getKeyPair[F[_]](keyPath: String, algo: SignAlgo)(implicit F: MonadError[F, Throwable]): F[KeyPair] = { + val keyFile = new File(keyPath) + val keyStorage = new FileKeyStorage[F](keyFile) + keyStorage.getOrCreateKeyPair(algo.generateKeyPair[F]().value.flatMap(F.fromEither)) + } +} diff --git a/jvm/src/main/scala/fluence/crypto/algorithm/AesCrypt.scala b/jvm/src/main/scala/fluence/crypto/algorithm/AesCrypt.scala index e4a1499..ef0d2c9 100644 --- a/jvm/src/main/scala/fluence/crypto/algorithm/AesCrypt.scala +++ b/jvm/src/main/scala/fluence/crypto/algorithm/AesCrypt.scala @@ -198,5 +198,4 @@ object AesCrypt extends slogging.LazyLogging { def apply[F[_] : Applicative, T](password: ByteVector, withIV: Boolean, config: AesConfig)(implicit ME: MonadError[F, Throwable], codec: Codec[F, T, Array[Byte]]): AesCrypt[F, T] = new AesCrypt(password.toHex.toCharArray, withIV, config) - } diff --git a/jvm/src/test/scala/fluence/crypto/AesSpec.scala b/jvm/src/test/scala/fluence/crypto/AesSpec.scala index 052a154..91200e1 100644 --- a/jvm/src/test/scala/fluence/crypto/AesSpec.scala +++ b/jvm/src/test/scala/fluence/crypto/AesSpec.scala @@ -1,12 +1,12 @@ package fluence.crypto -import fluence.crypto.algorithm.{AesConfig, AesCrypt, CryptoErr} +import fluence.crypto.algorithm.{ AesConfig, AesCrypt, CryptoErr } import cats.instances.try_._ import org.scalactic.source.Position -import org.scalatest.{Assertion, Matchers, WordSpec} +import org.scalatest.{ Assertion, Matchers, WordSpec } import scodec.bits.ByteVector -import scala.util.{Random, Try} +import scala.util.{ Random, Try } class AesSpec extends WordSpec with Matchers with slogging.LazyLogging { @@ -57,7 +57,7 @@ class AesSpec extends WordSpec with Matchers with slogging.LazyLogging { def checkCryptoError(tr: Try[String])(implicit pos: Position): Assertion = { tr.map(_ ⇒ false).recover { case e: CryptoErr ⇒ true - case e ⇒ + case e ⇒ logger.error("Unexpected error", e) false }.get shouldBe true diff --git a/src/main/scala/fluence/crypto/KeyStore.scala b/src/main/scala/fluence/crypto/KeyStore.scala index 30ed960..4923b5d 100644 --- a/src/main/scala/fluence/crypto/KeyStore.scala +++ b/src/main/scala/fluence/crypto/KeyStore.scala @@ -17,6 +17,10 @@ package fluence.crypto +import cats.Monad +import cats.syntax.functor._ +import cats.syntax.flatMap._ +import cats.data.EitherT import fluence.crypto.keypair.KeyPair import io.circe.{ Decoder, Encoder, HCursor, Json } import scodec.bits.{ Bases, ByteVector } @@ -63,16 +67,18 @@ object KeyStore { } yield KeyStore(KeyPair.fromByteVectors(public, secret)) } - def fromBase64(base64: String): KeyStore = { - val jsonStr = ByteVector.fromBase64(base64, alphabet) match { - case Some(bv) ⇒ new String(bv.toArray) - case None ⇒ - throw new IllegalArgumentException("'" + base64 + "' is not a valid base64.") - } - decode[Option[KeyStore]](jsonStr) match { - case Right(Some(ks)) ⇒ ks - case Right(None) ⇒ throw new IllegalArgumentException("'" + base64 + "' is not a valid key store.") - case Left(err) ⇒ throw new IllegalArgumentException("'" + base64 + "' is not a valid key store.", err) - } - } + def fromBase64[F[_] : Monad](base64: String): EitherT[F, IllegalArgumentException, KeyStore] = + for { + jsonStr ← ByteVector.fromBase64(base64, alphabet) match { + case Some(bv) ⇒ EitherT.pure[F, IllegalArgumentException](new String(bv.toArray)) + case None ⇒ EitherT.leftT( + new IllegalArgumentException("'" + base64 + "' is not a valid base64.") + ) + } + keyStore ← decode[Option[KeyStore]](jsonStr) match { + case Right(Some(ks)) ⇒ EitherT.pure[F, IllegalArgumentException](ks) + case Right(None) ⇒ EitherT.leftT[F, KeyStore](new IllegalArgumentException("'" + base64 + "' is not a valid key store.")) + case Left(err) ⇒ EitherT.leftT[F, KeyStore](new IllegalArgumentException("'" + base64 + "' is not a valid key store.", err)) + } + } yield keyStore } diff --git a/src/test/scala/fluence/crypto/KeyStoreSpec.scala b/src/test/scala/fluence/crypto/KeyStoreSpec.scala index 595985a..7cb13b1 100644 --- a/src/test/scala/fluence/crypto/KeyStoreSpec.scala +++ b/src/test/scala/fluence/crypto/KeyStoreSpec.scala @@ -54,21 +54,21 @@ class KeyStoreSpec extends WordSpec with Matchers { "throw an exception" when { "invalid base64 format" in { val invalidBase64 = "!@#$%" - the[IllegalArgumentException] thrownBy { - KeyStore.fromBase64(invalidBase64) - } should have message "'" + invalidBase64 + "' is not a valid base64." + + val e = KeyStore.fromBase64(invalidBase64).value.left.get + e should have message "'" + invalidBase64 + "' is not a valid base64." } "invalid decoded json" in { val invalidJson = ByteVector("""{"keystore":{"public":"cHViS2V5"}}""".getBytes).toBase64(alphabet) - the[IllegalArgumentException] thrownBy { - KeyStore.fromBase64(invalidJson) - } + + KeyStore.fromBase64(invalidJson).value.isLeft shouldBe true + } } "fetch KeyStore from valid base64" in { val invalidJson = ByteVector(jsonString.getBytes).toBase64(alphabet) - val result = KeyStore.fromBase64(invalidJson) + val result = KeyStore.fromBase64(invalidJson).value.right.get result shouldBe keyStore } }