From 17a59303992d70d4fd7165206bc53a7d97791247 Mon Sep 17 00:00:00 2001 From: Dmitry Kurinskiy Date: Mon, 12 Feb 2018 20:46:18 +0300 Subject: [PATCH] Contact jwt (#36) * EitherT in Crypto * main codebase compiles * tests compiles * crypto tests fixed * Contact test * In Node's codec, check that Kademlia Key conforms Contact's Public Key * b64url alphabet for PK encoding in JWT --- js/src/main/scala/Main.scala | 17 ++++ .../fluence/crypto/algorithm/Ecdsa.scala | 94 +++++++++---------- .../scala/fluence/crypto/SignatureSpec.scala | 50 +++++----- src/main/scala/fluence/crypto/SignAlgo.scala | 17 ++-- .../fluence/crypto/algorithm/DumbSign.scala | 34 +++++-- .../crypto/algorithm/KeyGenerator.scala | 8 +- .../crypto/algorithm/SignatureFunctions.scala | 8 +- .../crypto/signature/SignatureChecker.scala | 7 +- .../fluence/crypto/signature/Signer.scala | 16 ++-- 9 files changed, 153 insertions(+), 98 deletions(-) diff --git a/js/src/main/scala/Main.scala b/js/src/main/scala/Main.scala index a35d2e7..a9caac9 100644 --- a/js/src/main/scala/Main.scala +++ b/js/src/main/scala/Main.scala @@ -1,3 +1,20 @@ +/* + * Copyright (C) 2017 Fluence Labs Limited + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + object Main { def main(args: Array[String]): Unit = { println("Hello world!") diff --git a/jvm/src/main/scala/fluence/crypto/algorithm/Ecdsa.scala b/jvm/src/main/scala/fluence/crypto/algorithm/Ecdsa.scala index 4c3c414..f892a7a 100644 --- a/jvm/src/main/scala/fluence/crypto/algorithm/Ecdsa.scala +++ b/jvm/src/main/scala/fluence/crypto/algorithm/Ecdsa.scala @@ -21,7 +21,8 @@ import java.math.BigInteger import java.security._ import java.security.interfaces.ECPrivateKey -import cats.MonadError +import cats.data.EitherT +import cats.{ Applicative, Monad, MonadError } import cats.syntax.flatMap._ import cats.syntax.functor._ import fluence.crypto.keypair.KeyPair @@ -42,47 +43,62 @@ import scala.util.control.NonFatal class Ecdsa(curveType: String, scheme: String) extends JavaAlgorithm with SignatureFunctions with KeyGenerator { import Ecdsa._ - private def nonFatalHandling[F[_], A](a: ⇒ A)(errorText: String)(implicit F: MonadError[F, Throwable]): F[A] = { - try F.pure(a) + + private def nonFatalHandling[F[_] : Applicative, A](a: ⇒ A)(errorText: String): EitherT[F, CryptoErr, A] = { + try EitherT.pure(a) catch { - case NonFatal(e) ⇒ F.raiseError(CryptoErr(errorText + " " + e.getLocalizedMessage)) + case NonFatal(e) ⇒ EitherT.leftT(CryptoErr(errorText + " " + e.getLocalizedMessage)) } } - override def generateKeyPair[F[_]](random: SecureRandom)(implicit F: MonadError[F, Throwable]): F[KeyPair] = { + override def generateKeyPair[F[_] : Monad](random: SecureRandom): EitherT[F, CryptoErr, KeyPair] = for { - ecSpecOp ← F.pure(Option(ECNamedCurveTable.getParameterSpec(curveType))) - ecSpec ← ecSpecOp match { - case Some(ecs) ⇒ F.pure(ecs) - case None ⇒ F.raiseError[ECNamedCurveParameterSpec](CryptoErr("Parameter spec for the curve is not available.")) - } + ecSpec ← EitherT.fromOption[F]( + Option(ECNamedCurveTable.getParameterSpec(curveType)), + CryptoErr("Parameter spec for the curve is not available.")) g ← getKeyPairGenerator _ ← nonFatalHandling(g.initialize(ecSpec, random))("Could not initialize KeyPairGenerator.") - keyPair ← Option(g.generateKeyPair()) match { - case Some(p) ⇒ - //store S number for private key and compressed Q point on curve for public key - val pk = p.getPublic.asInstanceOf[ECPublicKey].getQ.getEncoded(true) - val sk = p.getPrivate.asInstanceOf[ECPrivateKey].getS.toByteArray - F.pure(KeyPair.fromBytes(pk, sk)) - case None ⇒ F.raiseError[KeyPair](CryptoErr("Could not generate KeyPair. Unexpected.")) - } + p ← EitherT.fromOption( + Option(g.generateKeyPair()), + CryptoErr("Could not generate KeyPair. Unexpected.")) + + keyPair ← nonFatalHandling { + //store S number for private key and compressed Q point on curve for public key + val pk = p.getPublic.asInstanceOf[ECPublicKey].getQ.getEncoded(true) + val sk = p.getPrivate.asInstanceOf[ECPrivateKey].getS.toByteArray + KeyPair.fromBytes(pk, sk) + }("Can't get public/private key from generated keypair") } yield keyPair - } - override def generateKeyPair[F[_]]()(implicit F: MonadError[F, Throwable]): F[KeyPair] = { + override def generateKeyPair[F[_] : Monad](): EitherT[F, CryptoErr, KeyPair] = generateKeyPair(new SecureRandom()) - } - override def sign[F[_]](keyPair: KeyPair, message: ByteVector)(implicit F: MonadError[F, Throwable]): F[fluence.crypto.signature.Signature] = { + override def sign[F[_] : Monad](keyPair: KeyPair, message: ByteVector): EitherT[F, CryptoErr, fluence.crypto.signature.Signature] = signMessage(keyPair.secretKey.value.toArray, message.toArray) .map(bb ⇒ fluence.crypto.signature.Signature(keyPair.publicKey, ByteVector(bb))) + + override def verify[F[_] : Monad](signature: fluence.crypto.signature.Signature, message: ByteVector): EitherT[F, CryptoErr, Unit] = { + val publicKey = signature.publicKey.value.toArray + val messageBytes = message.toArray + val signatureBytes = signature.sign.toArray + + for { + ec ← curveSpec + keySpec ← nonFatalHandling(new ECPublicKeySpec(ec.getCurve.decodePoint(publicKey), ec))("Cannot read public key.") + keyFactory ← getKeyFactory + signProvider ← getSignatureProvider + verify ← { + nonFatalHandling { + signProvider.initVerify(keyFactory.generatePublic(keySpec)) + signProvider.update(messageBytes) + signProvider.verify(signatureBytes) + }("Cannot verify message.") + } + _ ← EitherT.cond[F](verify, (), CryptoErr("Signature is not verified")) + } yield () } - override def verify[F[_]](signature: fluence.crypto.signature.Signature, message: ByteVector)(implicit F: MonadError[F, Throwable]): F[Boolean] = { - verifySign(signature.publicKey.value.toArray, message.toArray, signature.sign.toArray) - } - - private def signMessage[F[_]](privateKey: Array[Byte], message: Array[Byte])(implicit F: MonadError[F, Throwable]): F[Array[Byte]] = { + private def signMessage[F[_] : Monad](privateKey: Array[Byte], message: Array[Byte]): EitherT[F, CryptoErr, Array[Byte]] = { for { ec ← curveSpec keySpec ← nonFatalHandling(new ECPrivateKeySpec(new BigInteger(privateKey), ec))("Cannot read private key.") @@ -98,32 +114,16 @@ class Ecdsa(curveType: String, scheme: String) extends JavaAlgorithm } yield sign } - private def verifySign[F[_]](publicKey: Array[Byte], message: Array[Byte], signature: Array[Byte])(implicit F: MonadError[F, Throwable]): F[Boolean] = { - for { - ec ← curveSpec - keySpec ← nonFatalHandling(new ECPublicKeySpec(ec.getCurve.decodePoint(publicKey), ec))("Cannot read public key.") - keyFactory ← getKeyFactory - signProvider ← getSignatureProvider - verify ← { - nonFatalHandling { - signProvider.initVerify(keyFactory.generatePublic(keySpec)) - signProvider.update(message) - signProvider.verify(signature) - }("Cannot verify message.") - } - } yield verify - } - - private def curveSpec[F[_]](implicit F: MonadError[F, Throwable]) = + private def curveSpec[F[_] : Monad] = nonFatalHandling(ECNamedCurveTable.getParameterSpec(curveType).asInstanceOf[ECParameterSpec])("Cannot get curve parameters.") - private def getKeyPairGenerator[F[_]](implicit F: MonadError[F, Throwable]) = + private def getKeyPairGenerator[F[_] : Monad] = nonFatalHandling(KeyPairGenerator.getInstance(ECDSA, BouncyCastleProvider.PROVIDER_NAME))("Cannot get key pair generator.") - private def getKeyFactory[F[_]](implicit F: MonadError[F, Throwable]) = + private def getKeyFactory[F[_] : Monad] = nonFatalHandling(KeyFactory.getInstance(ECDSA, BouncyCastleProvider.PROVIDER_NAME))("Cannot get key factory instance.") - private def getSignatureProvider[F[_]](implicit F: MonadError[F, Throwable]) = + private def getSignatureProvider[F[_] : Monad] = nonFatalHandling(Signature.getInstance(scheme, BouncyCastleProvider.PROVIDER_NAME))("Cannot get signature instance.") } diff --git a/jvm/src/test/scala/fluence/crypto/SignatureSpec.scala b/jvm/src/test/scala/fluence/crypto/SignatureSpec.scala index 661e1a1..7248996 100644 --- a/jvm/src/test/scala/fluence/crypto/SignatureSpec.scala +++ b/jvm/src/test/scala/fluence/crypto/SignatureSpec.scala @@ -18,68 +18,74 @@ package fluence.crypto import java.io.File -import java.security.SecureRandom +import cats.data.EitherT import cats.instances.try_._ import fluence.crypto.algorithm.{ CryptoErr, Ecdsa } -import fluence.crypto.keypair.KeyPair import org.scalatest.{ BeforeAndAfterAll, Matchers, WordSpec } import scodec.bits.ByteVector -import scala.util.Random +import scala.util.{ Random, Try } class SignatureSpec extends WordSpec with Matchers with BeforeAndAfterAll { def rndBytes(size: Int) = Random.nextString(10).getBytes + def rndByteVector(size: Int) = ByteVector(rndBytes(size)) + private implicit class TryEitherTExtractor[A, B](et: EitherT[Try, A, B]) { + def extract: B = et.value.get.right.get + + def isOk: Boolean = et.value.fold(_ ⇒ false, _.isRight) + } + "ecdsa algorithm" should { "correct sign and verify data" in { val algorithm = Ecdsa.ecdsa_secp256k1_sha256 - val keys = algorithm.generateKeyPair().get + val keys = algorithm.generateKeyPair[Try]().extract val data = rndByteVector(10) - val sign = algorithm.sign(keys, data).get + val sign = algorithm.sign[Try](keys, data).extract - algorithm.verify(sign, data).get shouldBe true + algorithm.verify[Try](sign, data).isOk shouldBe true val randomData = rndByteVector(10) - val randomSign = algorithm.sign(keys, randomData).get + val randomSign = algorithm.sign(keys, randomData).extract - algorithm.verify(sign.copy(sign = randomSign.sign), data).get shouldBe false + algorithm.verify(sign.copy(sign = randomSign.sign), data).isOk shouldBe false - algorithm.verify(sign, randomData).get shouldBe false + algorithm.verify(sign, randomData).isOk shouldBe false } "correctly work with signer and checker" in { val algo = new SignAlgo(Ecdsa.ecdsa_secp256k1_sha256) - val keys = algo.generateKeyPair().get + val keys = algo.generateKeyPair().extract val signer = algo.signer(keys) val data = rndByteVector(10) - val sign = signer.sign(data).get + val sign = signer.sign(data).extract - algo.checker.check(sign, data).get shouldBe true + algo.checker.check(sign, data).isOk shouldBe true - val randomSign = signer.sign(rndByteVector(10)).get - algo.checker.check(randomSign, data).get shouldBe false + val randomSign = signer.sign(rndByteVector(10)).extract + algo.checker.check(randomSign, data).isOk shouldBe false } "throw an errors on invalid data" in { val algo = new SignAlgo(Ecdsa.ecdsa_secp256k1_sha256) - val keys = algo.generateKeyPair().get + val keys = algo.generateKeyPair().extract val signer = algo.signer(keys) val data = rndByteVector(10) - val sign = signer.sign(data).get + val sign = signer.sign(data).extract - the[CryptoErr] thrownBy algo.checker.check(sign.copy(sign = rndByteVector(10)), data).get - the[CryptoErr] thrownBy algo.checker.check(sign.copy(publicKey = sign.publicKey.copy(value = rndByteVector(10))), data).get + the[CryptoErr] thrownBy algo.checker.check(sign.copy(sign = rndByteVector(10)), data).value.flatMap(_.toTry).get + the[CryptoErr] thrownBy algo.checker.check(sign.copy(publicKey = sign.publicKey.copy(value = rndByteVector(10))), data).value.flatMap(_.toTry).get } "store and read key from file" in { val algo = new SignAlgo(Ecdsa.ecdsa_secp256k1_sha256) - val keys = algo.generateKeyPair().get + val keys = algo.generateKeyPair().extract val keyFile = File.createTempFile("test", "") if (keyFile.exists()) keyFile.delete() @@ -92,10 +98,10 @@ class SignatureSpec extends WordSpec with Matchers with BeforeAndAfterAll { val signer = algo.signer(keys) val data = rndByteVector(10) - val sign = signer.sign(data).get + val sign = signer.sign(data).extract - algo.checker.check(sign.copy(publicKey = keysRead.publicKey), data).get shouldBe true - algo.checker.check(sign, data).get shouldBe true + algo.checker.check(sign.copy(publicKey = keysRead.publicKey), data).isOk shouldBe true + algo.checker.check(sign, data).isOk shouldBe true //try to store key into previously created file storage.storeSecretKey(keys).isFailure shouldBe true diff --git a/src/main/scala/fluence/crypto/SignAlgo.scala b/src/main/scala/fluence/crypto/SignAlgo.scala index 32fd9f9..7c3b55b 100644 --- a/src/main/scala/fluence/crypto/SignAlgo.scala +++ b/src/main/scala/fluence/crypto/SignAlgo.scala @@ -19,8 +19,9 @@ package fluence.crypto import java.security.SecureRandom -import cats.MonadError -import fluence.crypto.algorithm.{ DumbSign, KeyGenerator, SignatureFunctions } +import cats.data.EitherT +import cats.{ Monad, MonadError } +import fluence.crypto.algorithm.{ CryptoErr, DumbSign, KeyGenerator, SignatureFunctions } import fluence.crypto.keypair.KeyPair import fluence.crypto.signature.{ Signature, SignatureChecker, Signer } import scodec.bits.ByteVector @@ -33,15 +34,17 @@ import scala.language.higherKinds */ class SignAlgo(algo: KeyGenerator with SignatureFunctions) { - def generateKeyPair[F[_]]()(implicit F: MonadError[F, Throwable]): F[KeyPair] = algo.generateKeyPair() - def generateKeyPair[F[_]](seed: ByteVector)(implicit F: MonadError[F, Throwable]): F[KeyPair] = algo.generateKeyPair(new SecureRandom(seed.toArray)) + def generateKeyPair[F[_] : Monad](): EitherT[F, CryptoErr, KeyPair] = algo.generateKeyPair() + def generateKeyPair[F[_] : Monad](seed: ByteVector): EitherT[F, CryptoErr, KeyPair] = algo.generateKeyPair(new SecureRandom(seed.toArray)) - def signer[F[_]](kp: KeyPair)(implicit F: MonadError[F, Throwable]): Signer[F] = new Signer[F] { - override def sign(plain: ByteVector): F[Signature] = algo.sign(kp, plain) + def signer(kp: KeyPair): Signer = new Signer { override def publicKey: KeyPair.Public = kp.publicKey + override def sign[F[_] : Monad](plain: ByteVector): EitherT[F, CryptoErr, Signature] = algo.sign(kp, plain) } - def checker[F[_]](implicit F: MonadError[F, Throwable]): SignatureChecker[F] = (signature: Signature, plain: ByteVector) ⇒ algo.verify(signature, plain) + def checker: SignatureChecker = new SignatureChecker { + override def check[F[_] : Monad](signature: Signature, plain: ByteVector): EitherT[F, CryptoErr, Unit] = algo.verify(signature, plain) + } } object SignAlgo { diff --git a/src/main/scala/fluence/crypto/algorithm/DumbSign.scala b/src/main/scala/fluence/crypto/algorithm/DumbSign.scala index ed52bda..22bcd81 100644 --- a/src/main/scala/fluence/crypto/algorithm/DumbSign.scala +++ b/src/main/scala/fluence/crypto/algorithm/DumbSign.scala @@ -1,8 +1,26 @@ +/* + * Copyright (C) 2017 Fluence Labs Limited + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + package fluence.crypto.algorithm import java.security.SecureRandom -import cats.MonadError +import cats.Monad +import cats.data.EitherT import fluence.crypto.keypair.KeyPair import fluence.crypto.signature.Signature import scodec.bits.ByteVector @@ -10,15 +28,15 @@ import scodec.bits.ByteVector import scala.language.higherKinds class DumbSign extends KeyGenerator with SignatureFunctions { - override def generateKeyPair[F[_]](random: SecureRandom)(implicit F: MonadError[F, Throwable]): F[KeyPair] = - F.pure(KeyPair.fromBytes(random.generateSeed(10), random.generateSeed(10))) + override def generateKeyPair[F[_] : Monad](random: SecureRandom): EitherT[F, CryptoErr, KeyPair] = + EitherT.pure(KeyPair.fromBytes(random.generateSeed(10), random.generateSeed(10))) - override def generateKeyPair[F[_]]()(implicit F: MonadError[F, Throwable]): F[KeyPair] = + override def generateKeyPair[F[_] : Monad](): EitherT[F, CryptoErr, KeyPair] = generateKeyPair(new SecureRandom()) - override def sign[F[_]](keyPair: KeyPair, message: ByteVector)(implicit F: MonadError[F, Throwable]): F[Signature] = - F.pure(Signature(keyPair.publicKey, message.reverse)) + override def sign[F[_] : Monad](keyPair: KeyPair, message: ByteVector): EitherT[F, CryptoErr, Signature] = + EitherT.pure(Signature(keyPair.publicKey, message.reverse)) - override def verify[F[_]](signature: Signature, message: ByteVector)(implicit F: MonadError[F, Throwable]): F[Boolean] = - F.pure(signature.sign == message.reverse) + override def verify[F[_] : Monad](signature: Signature, message: ByteVector): EitherT[F, CryptoErr, Unit] = + EitherT.cond[F](signature.sign == message.reverse, (), CryptoErr("Invalid Signature")) } diff --git a/src/main/scala/fluence/crypto/algorithm/KeyGenerator.scala b/src/main/scala/fluence/crypto/algorithm/KeyGenerator.scala index 7a33bea..e30574b 100644 --- a/src/main/scala/fluence/crypto/algorithm/KeyGenerator.scala +++ b/src/main/scala/fluence/crypto/algorithm/KeyGenerator.scala @@ -19,12 +19,14 @@ package fluence.crypto.algorithm import java.security.SecureRandom -import cats.MonadError +import cats.Monad +import cats.data.EitherT import fluence.crypto.keypair.KeyPair import scala.language.higherKinds trait KeyGenerator { - def generateKeyPair[F[_]](random: SecureRandom)(implicit F: MonadError[F, Throwable]): F[KeyPair] - def generateKeyPair[F[_]]()(implicit F: MonadError[F, Throwable]): F[KeyPair] + def generateKeyPair[F[_] : Monad](random: SecureRandom): EitherT[F, CryptoErr, KeyPair] + + def generateKeyPair[F[_] : Monad](): EitherT[F, CryptoErr, KeyPair] } diff --git a/src/main/scala/fluence/crypto/algorithm/SignatureFunctions.scala b/src/main/scala/fluence/crypto/algorithm/SignatureFunctions.scala index a76740f..7b97174 100644 --- a/src/main/scala/fluence/crypto/algorithm/SignatureFunctions.scala +++ b/src/main/scala/fluence/crypto/algorithm/SignatureFunctions.scala @@ -17,7 +17,8 @@ package fluence.crypto.algorithm -import cats.MonadError +import cats.Monad +import cats.data.EitherT import fluence.crypto.keypair.KeyPair import fluence.crypto.signature.Signature import scodec.bits.ByteVector @@ -25,6 +26,7 @@ import scodec.bits.ByteVector import scala.language.higherKinds trait SignatureFunctions { - def sign[F[_]](keyPair: KeyPair, message: ByteVector)(implicit F: MonadError[F, Throwable]): F[Signature] - def verify[F[_]](signature: Signature, message: ByteVector)(implicit F: MonadError[F, Throwable]): F[Boolean] + def sign[F[_] : Monad](keyPair: KeyPair, message: ByteVector): EitherT[F, CryptoErr, Signature] + + def verify[F[_] : Monad](signature: Signature, message: ByteVector): EitherT[F, CryptoErr, Unit] } diff --git a/src/main/scala/fluence/crypto/signature/SignatureChecker.scala b/src/main/scala/fluence/crypto/signature/SignatureChecker.scala index 2a55a76..75cf39b 100644 --- a/src/main/scala/fluence/crypto/signature/SignatureChecker.scala +++ b/src/main/scala/fluence/crypto/signature/SignatureChecker.scala @@ -17,10 +17,13 @@ package fluence.crypto.signature +import cats.Monad +import cats.data.EitherT +import fluence.crypto.algorithm.CryptoErr import scodec.bits.ByteVector import scala.language.higherKinds -trait SignatureChecker[F[_]] { - def check(signature: Signature, plain: ByteVector): F[Boolean] +trait SignatureChecker { + def check[F[_] : Monad](signature: Signature, plain: ByteVector): EitherT[F, CryptoErr, Unit] } diff --git a/src/main/scala/fluence/crypto/signature/Signer.scala b/src/main/scala/fluence/crypto/signature/Signer.scala index e70c171..3b3b435 100644 --- a/src/main/scala/fluence/crypto/signature/Signer.scala +++ b/src/main/scala/fluence/crypto/signature/Signer.scala @@ -17,23 +17,27 @@ package fluence.crypto.signature -import cats.MonadError +import cats.Monad +import cats.data.EitherT +import fluence.crypto.algorithm.CryptoErr import fluence.crypto.keypair.KeyPair import scodec.bits.ByteVector import scala.language.higherKinds -trait Signer[F[_]] { +trait Signer { def publicKey: KeyPair.Public - def sign(plain: ByteVector): F[Signature] + def sign[F[_] : Monad](plain: ByteVector): EitherT[F, CryptoErr, Signature] } object Signer { - class DumbSigner[F[_]](keyPair: KeyPair)(implicit F: MonadError[F, Throwable]) extends Signer[F] { + + class DumbSigner(keyPair: KeyPair) extends Signer { override def publicKey: KeyPair.Public = keyPair.publicKey - override def sign(plain: ByteVector): F[Signature] = - F.pure(Signature(keyPair.publicKey, plain.reverse)) + override def sign[F[_] : Monad](plain: ByteVector): EitherT[F, CryptoErr, Signature] = + EitherT.pure(Signature(keyPair.publicKey, plain.reverse)) } + }