From 79738aa2c78b11aa9f9e0d528d1e0ae01323cab5 Mon Sep 17 00:00:00 2001 From: Dima Date: Fri, 9 Feb 2018 16:11:03 +0300 Subject: [PATCH] refactor sign classes (#34) * refactor sign classes store in private key only S, in public key only Q point without wrappers * refactor, class ...[F[_]] to method ...[F[_]] * remove redundant --- .../fluence/crypto/algorithm/Ecdsa.scala | 61 +++++++++---------- .../crypto/algorithm/JavaAlgorithm.scala | 11 ---- .../scala/fluence/crypto/SignatureSpec.scala | 33 +++++----- .../scala/fluence/crypto/FileKeyStorage.scala | 2 + src/main/scala/fluence/crypto/SignAlgo.scala | 49 +++++++++++++++ .../fluence/crypto/algorithm/DumbSign.scala | 24 ++++++++ .../crypto/algorithm/KeyGenerator.scala | 7 ++- .../crypto/algorithm/SignatureFunctions.scala | 9 ++- .../crypto/signature/SignatureChecker.scala | 12 +--- .../fluence/crypto/signature/Signer.scala | 8 +-- 10 files changed, 135 insertions(+), 81 deletions(-) create mode 100644 src/main/scala/fluence/crypto/SignAlgo.scala create mode 100644 src/main/scala/fluence/crypto/algorithm/DumbSign.scala diff --git a/jvm/src/main/scala/fluence/crypto/algorithm/Ecdsa.scala b/jvm/src/main/scala/fluence/crypto/algorithm/Ecdsa.scala index 7c8ff23..f48e77e 100644 --- a/jvm/src/main/scala/fluence/crypto/algorithm/Ecdsa.scala +++ b/jvm/src/main/scala/fluence/crypto/algorithm/Ecdsa.scala @@ -17,17 +17,18 @@ package fluence.crypto.algorithm +import java.math.BigInteger import java.security._ -import java.security.spec.{ PKCS8EncodedKeySpec, X509EncodedKeySpec } +import java.security.interfaces.ECPrivateKey import cats.MonadError import cats.syntax.flatMap._ import cats.syntax.functor._ import fluence.crypto.keypair.KeyPair -import fluence.crypto.signature.SignatureChecker import org.bouncycastle.jce.ECNamedCurveTable +import org.bouncycastle.jce.interfaces.ECPublicKey import org.bouncycastle.jce.provider.BouncyCastleProvider -import org.bouncycastle.jce.spec.ECNamedCurveParameterSpec +import org.bouncycastle.jce.spec.{ ECNamedCurveParameterSpec, ECParameterSpec, ECPrivateKeySpec, ECPublicKeySpec } import scodec.bits.ByteVector import scala.language.higherKinds @@ -38,18 +39,17 @@ import scala.util.control.NonFatal * @param curveType http://www.bouncycastle.org/wiki/display/JA1/Supported+Curves+%28ECDSA+and+ECGOST%29 * @param scheme https://bouncycastle.org/specifications.html */ -class Ecdsa[F[_]](curveType: String, scheme: String)(implicit F: MonadError[F, Throwable]) extends JavaAlgorithm - with SignatureFunctions[F] with KeyGenerator[F] { +class Ecdsa(curveType: String, scheme: String) extends JavaAlgorithm + with SignatureFunctions with KeyGenerator { import Ecdsa._ - private def nonFatalHandling[A](a: ⇒ A)(errorText: String): F[A] = { + private def nonFatalHandling[F[_], A](a: ⇒ A)(errorText: String)(implicit F: MonadError[F, Throwable]): F[A] = { try F.pure(a) catch { case NonFatal(e) ⇒ F.raiseError(CryptoErr(errorText + " " + e.getLocalizedMessage)) } } - override def generateKeyPair(random: SecureRandom): F[KeyPair] = { - import JavaAlgorithm._ + override def generateKeyPair[F[_]](random: SecureRandom)(implicit F: MonadError[F, Throwable]): F[KeyPair] = { for { ecSpecOp ← F.pure(Option(ECNamedCurveTable.getParameterSpec(curveType))) ecSpec ← ecSpecOp match { @@ -59,28 +59,33 @@ class Ecdsa[F[_]](curveType: String, scheme: String)(implicit F: MonadError[F, T g ← getKeyPairGenerator _ ← nonFatalHandling(g.initialize(ecSpec, random))("Could not initialize KeyPairGenerator.") keyPair ← Option(g.generateKeyPair()) match { - case Some(p) ⇒ F.pure(p) - case None ⇒ F.raiseError[java.security.KeyPair](CryptoErr("Could not generate KeyPair. Unexpected.")) + 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.")) } } yield keyPair } - override def generateKeyPair(): F[KeyPair] = { + override def generateKeyPair[F[_]]()(implicit F: MonadError[F, Throwable]): F[KeyPair] = { generateKeyPair(new SecureRandom()) } - override def sign(keyPair: KeyPair, message: ByteVector): F[fluence.crypto.signature.Signature] = { + override def sign[F[_]](keyPair: KeyPair, message: ByteVector)(implicit F: MonadError[F, Throwable]): F[fluence.crypto.signature.Signature] = { signMessage(keyPair.secretKey.value.toArray, message.toArray) .map(bb ⇒ fluence.crypto.signature.Signature(keyPair.publicKey, ByteVector(bb))) } - override def verify(signature: fluence.crypto.signature.Signature, message: ByteVector): F[Boolean] = { + 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(privateKey: Array[Byte], message: Array[Byte]): F[Array[Byte]] = { - val keySpec = new PKCS8EncodedKeySpec(privateKey) + private def signMessage[F[_]](privateKey: Array[Byte], message: Array[Byte])(implicit F: MonadError[F, Throwable]): F[Array[Byte]] = { for { + ec ← curveSpec + keySpec ← nonFatalHandling(new ECPrivateKeySpec(new BigInteger(privateKey), ec))("Cannot read private key.") keyFactory ← getKeyFactory signProvider ← getSignatureProvider sign ← { @@ -93,9 +98,10 @@ class Ecdsa[F[_]](curveType: String, scheme: String)(implicit F: MonadError[F, T } yield sign } - private def verifySign(publicKey: Array[Byte], message: Array[Byte], signature: Array[Byte]): F[Boolean] = { - val keySpec = new X509EncodedKeySpec(publicKey) + 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 ← { @@ -108,13 +114,16 @@ class Ecdsa[F[_]](curveType: String, scheme: String)(implicit F: MonadError[F, T } yield verify } - private lazy val getKeyPairGenerator = + private def curveSpec[F[_]](implicit F: MonadError[F, Throwable]) = + nonFatalHandling(ECNamedCurveTable.getParameterSpec(curveType).asInstanceOf[ECParameterSpec])("Cannot get curve parameters.") + + private def getKeyPairGenerator[F[_]](implicit F: MonadError[F, Throwable]) = nonFatalHandling(KeyPairGenerator.getInstance(ECDSA, BouncyCastleProvider.PROVIDER_NAME))("Cannot get key pair generator.") - private lazy val getKeyFactory = + private def getKeyFactory[F[_]](implicit F: MonadError[F, Throwable]) = nonFatalHandling(KeyFactory.getInstance(ECDSA, BouncyCastleProvider.PROVIDER_NAME))("Cannot get key factory instance.") - private lazy val getSignatureProvider = + private def getSignatureProvider[F[_]](implicit F: MonadError[F, Throwable]) = nonFatalHandling(Signature.getInstance(scheme, BouncyCastleProvider.PROVIDER_NAME))("Cannot get signature instance.") } @@ -128,16 +137,4 @@ object Ecdsa { * `SHA256withECDSA` Preferably the size of the key is greater than or equal to the digest algorithm */ def ecdsa_secp256k1_sha256[F[_]](implicit F: MonadError[F, Throwable]) = new Ecdsa("secp256k1", "SHA256withECDSA") - - class Signer(keyPair: KeyPair) extends fluence.crypto.signature.Signer { - override def publicKey: KeyPair.Public = keyPair.publicKey - - override def sign[F[_]](plain: ByteVector)(implicit F: MonadError[F, Throwable]): F[fluence.crypto.signature.Signature] = - Ecdsa.ecdsa_secp256k1_sha256.sign(keyPair, plain) - } - - case object Checker extends SignatureChecker { - override def check[F[_]](signature: fluence.crypto.signature.Signature, plain: ByteVector)(implicit F: MonadError[F, Throwable]): F[Boolean] = - Ecdsa.ecdsa_secp256k1_sha256.verify(signature, plain) - } } diff --git a/jvm/src/main/scala/fluence/crypto/algorithm/JavaAlgorithm.scala b/jvm/src/main/scala/fluence/crypto/algorithm/JavaAlgorithm.scala index 1cf04f1..bdd8de0 100644 --- a/jvm/src/main/scala/fluence/crypto/algorithm/JavaAlgorithm.scala +++ b/jvm/src/main/scala/fluence/crypto/algorithm/JavaAlgorithm.scala @@ -19,9 +19,7 @@ package fluence.crypto.algorithm import java.security.Security -import fluence.crypto.keypair.KeyPair import org.bouncycastle.jce.provider.BouncyCastleProvider -import scodec.bits.ByteVector import scala.language.{ higherKinds, implicitConversions } @@ -33,15 +31,6 @@ private[crypto] trait JavaAlgorithm extends Algorithm { } object JavaAlgorithm { - /** - * getEncoded methods return ASN.1 encoding in PKCS#8 standard for secret key and X.509 standard for public - * ASN.1 serialization standard defined by ISO. https://en.wikipedia.org/wiki/Abstract_Syntax_Notation_One - * PKCS #8 is a standard syntax for storing private key information. https://en.wikipedia.org/wiki/PKCS_8 - * X.509 is a standard that defines the format of public key certificates. https://en.wikipedia.org/wiki/X.509 - */ - implicit def jKeyPairToKeyPair(jKeyPair: java.security.KeyPair): KeyPair = - KeyPair(KeyPair.Public(ByteVector(jKeyPair.getPublic.getEncoded)), KeyPair.Secret(ByteVector(jKeyPair.getPrivate.getEncoded))) - /** * add JVM-specific security provider in class loader */ diff --git a/jvm/src/test/scala/fluence/crypto/SignatureSpec.scala b/jvm/src/test/scala/fluence/crypto/SignatureSpec.scala index cc05f46..661e1a1 100644 --- a/jvm/src/test/scala/fluence/crypto/SignatureSpec.scala +++ b/jvm/src/test/scala/fluence/crypto/SignatureSpec.scala @@ -52,37 +52,34 @@ class SignatureSpec extends WordSpec with Matchers with BeforeAndAfterAll { } "correctly work with signer and checker" in { - val keys = Ecdsa.ecdsa_secp256k1_sha256.generateKeyPair().get - val signer = new Ecdsa.Signer(keys) + val algo = new SignAlgo(Ecdsa.ecdsa_secp256k1_sha256) + val keys = algo.generateKeyPair().get + val signer = algo.signer(keys) val data = rndByteVector(10) val sign = signer.sign(data).get - Ecdsa.Checker.check(sign, data).get shouldBe true + algo.checker.check(sign, data).get shouldBe true val randomSign = signer.sign(rndByteVector(10)).get - Ecdsa.Checker.check(randomSign, data).get shouldBe false + algo.checker.check(randomSign, data).get shouldBe false } "throw an errors on invalid data" in { - val keys = Ecdsa.ecdsa_secp256k1_sha256.generateKeyPair().get - val signer = new Ecdsa.Signer(keys) + val algo = new SignAlgo(Ecdsa.ecdsa_secp256k1_sha256) + val keys = algo.generateKeyPair().get + val signer = algo.signer(keys) val data = rndByteVector(10) - val brokenSecret = keys.copy(secretKey = KeyPair.Secret(rndByteVector(10))) - val brokenSigner = new Ecdsa.Signer(brokenSecret) - - the[CryptoErr] thrownBy brokenSigner.sign(data).get - val sign = signer.sign(data).get - the[CryptoErr] thrownBy Ecdsa.Checker.check(sign.copy(sign = rndByteVector(10)), data).get - the[CryptoErr] thrownBy Ecdsa.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).get + the[CryptoErr] thrownBy algo.checker.check(sign.copy(publicKey = sign.publicKey.copy(value = rndByteVector(10))), data).get } "store and read key from file" in { - val seed = rndBytes(32) - val keys = Ecdsa.ecdsa_secp256k1_sha256.generateKeyPair(new SecureRandom(seed)).get + val algo = new SignAlgo(Ecdsa.ecdsa_secp256k1_sha256) + val keys = algo.generateKeyPair().get val keyFile = File.createTempFile("test", "") if (keyFile.exists()) keyFile.delete() @@ -93,12 +90,12 @@ class SignatureSpec extends WordSpec with Matchers with BeforeAndAfterAll { val keysReadE = storage.readKeyPair val keysRead = keysReadE.get - val signer = new Ecdsa.Signer(keys) + val signer = algo.signer(keys) val data = rndByteVector(10) val sign = signer.sign(data).get - Ecdsa.Checker.check(sign.copy(publicKey = keysRead.publicKey), data).get shouldBe true - Ecdsa.Checker.check(sign, data).get shouldBe true + algo.checker.check(sign.copy(publicKey = keysRead.publicKey), data).get shouldBe true + algo.checker.check(sign, data).get shouldBe true //try to store key into previously created file storage.storeSecretKey(keys).isFailure shouldBe true diff --git a/src/main/scala/fluence/crypto/FileKeyStorage.scala b/src/main/scala/fluence/crypto/FileKeyStorage.scala index 44618a0..6e15f82 100644 --- a/src/main/scala/fluence/crypto/FileKeyStorage.scala +++ b/src/main/scala/fluence/crypto/FileKeyStorage.scala @@ -29,6 +29,8 @@ import io.circe.syntax._ import io.circe.{ Decoder, Encoder, HCursor, Json } import scodec.bits.ByteVector +import scala.language.higherKinds + case class KeyStore(keyPair: KeyPair) class FileKeyStorage[F[_]](file: File)(implicit F: MonadError[F, Throwable]) { diff --git a/src/main/scala/fluence/crypto/SignAlgo.scala b/src/main/scala/fluence/crypto/SignAlgo.scala new file mode 100644 index 0000000..32fd9f9 --- /dev/null +++ b/src/main/scala/fluence/crypto/SignAlgo.scala @@ -0,0 +1,49 @@ +/* + * 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 + +import java.security.SecureRandom + +import cats.MonadError +import fluence.crypto.algorithm.{ DumbSign, KeyGenerator, SignatureFunctions } +import fluence.crypto.keypair.KeyPair +import fluence.crypto.signature.{ Signature, SignatureChecker, Signer } +import scodec.bits.ByteVector + +import scala.language.higherKinds + +/** + * Class for generation keys, signers and checkers + * @param algo implementation of sign alghoritms, e.g. ECDSA + */ +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 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) + override def publicKey: KeyPair.Public = kp.publicKey + } + + def checker[F[_]](implicit F: MonadError[F, Throwable]): SignatureChecker[F] = (signature: Signature, plain: ByteVector) ⇒ algo.verify(signature, plain) +} + +object SignAlgo { + val dumb = new SignAlgo(new DumbSign()) +} diff --git a/src/main/scala/fluence/crypto/algorithm/DumbSign.scala b/src/main/scala/fluence/crypto/algorithm/DumbSign.scala new file mode 100644 index 0000000..ed52bda --- /dev/null +++ b/src/main/scala/fluence/crypto/algorithm/DumbSign.scala @@ -0,0 +1,24 @@ +package fluence.crypto.algorithm + +import java.security.SecureRandom + +import cats.MonadError +import fluence.crypto.keypair.KeyPair +import fluence.crypto.signature.Signature +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[_]]()(implicit F: MonadError[F, Throwable]): F[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 verify[F[_]](signature: Signature, message: ByteVector)(implicit F: MonadError[F, Throwable]): F[Boolean] = + F.pure(signature.sign == message.reverse) +} diff --git a/src/main/scala/fluence/crypto/algorithm/KeyGenerator.scala b/src/main/scala/fluence/crypto/algorithm/KeyGenerator.scala index baec2da..7a33bea 100644 --- a/src/main/scala/fluence/crypto/algorithm/KeyGenerator.scala +++ b/src/main/scala/fluence/crypto/algorithm/KeyGenerator.scala @@ -19,11 +19,12 @@ package fluence.crypto.algorithm import java.security.SecureRandom +import cats.MonadError import fluence.crypto.keypair.KeyPair import scala.language.higherKinds -trait KeyGenerator[F[_]] { - def generateKeyPair(random: SecureRandom): F[KeyPair] - def generateKeyPair(): F[KeyPair] +trait KeyGenerator { + def generateKeyPair[F[_]](random: SecureRandom)(implicit F: MonadError[F, Throwable]): F[KeyPair] + def generateKeyPair[F[_]]()(implicit F: MonadError[F, Throwable]): F[KeyPair] } diff --git a/src/main/scala/fluence/crypto/algorithm/SignatureFunctions.scala b/src/main/scala/fluence/crypto/algorithm/SignatureFunctions.scala index 3e9d1ad..a76740f 100644 --- a/src/main/scala/fluence/crypto/algorithm/SignatureFunctions.scala +++ b/src/main/scala/fluence/crypto/algorithm/SignatureFunctions.scala @@ -17,11 +17,14 @@ package fluence.crypto.algorithm +import cats.MonadError import fluence.crypto.keypair.KeyPair import fluence.crypto.signature.Signature import scodec.bits.ByteVector -trait SignatureFunctions[F[_]] { - def sign(keyPair: KeyPair, message: ByteVector): F[Signature] - def verify(signature: Signature, message: ByteVector): F[Boolean] +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] } diff --git a/src/main/scala/fluence/crypto/signature/SignatureChecker.scala b/src/main/scala/fluence/crypto/signature/SignatureChecker.scala index 2b5e103..2a55a76 100644 --- a/src/main/scala/fluence/crypto/signature/SignatureChecker.scala +++ b/src/main/scala/fluence/crypto/signature/SignatureChecker.scala @@ -17,18 +17,10 @@ package fluence.crypto.signature -import cats.MonadError import scodec.bits.ByteVector import scala.language.higherKinds -trait SignatureChecker { - def check[F[_]](signature: Signature, plain: ByteVector)(implicit F: MonadError[F, Throwable]): F[Boolean] -} - -object SignatureChecker { - case object DumbChecker extends SignatureChecker { - override def check[F[_]](signature: Signature, plain: ByteVector)(implicit F: MonadError[F, Throwable]): F[Boolean] = - F.pure(signature.sign == plain.reverse) - } +trait SignatureChecker[F[_]] { + def check(signature: Signature, plain: ByteVector): F[Boolean] } diff --git a/src/main/scala/fluence/crypto/signature/Signer.scala b/src/main/scala/fluence/crypto/signature/Signer.scala index d9dfb72..e70c171 100644 --- a/src/main/scala/fluence/crypto/signature/Signer.scala +++ b/src/main/scala/fluence/crypto/signature/Signer.scala @@ -23,17 +23,17 @@ import scodec.bits.ByteVector import scala.language.higherKinds -trait Signer { +trait Signer[F[_]] { def publicKey: KeyPair.Public - def sign[F[_]](plain: ByteVector)(implicit F: MonadError[F, Throwable]): F[Signature] + def sign(plain: ByteVector): F[Signature] } object Signer { - class DumbSigner(keyPair: KeyPair) extends Signer { + class DumbSigner[F[_]](keyPair: KeyPair)(implicit F: MonadError[F, Throwable]) extends Signer[F] { override def publicKey: KeyPair.Public = keyPair.publicKey - override def sign[F[_]](plain: ByteVector)(implicit F: MonadError[F, Throwable]): F[Signature] = + override def sign(plain: ByteVector): F[Signature] = F.pure(Signature(keyPair.publicKey, plain.reverse)) } }