WIP cleanup but working well
All checks were successful
/ Build and Test Library Snapshot (pull_request) Successful in 2m25s
All checks were successful
/ Build and Test Library Snapshot (pull_request) Successful in 2m25s
This commit is contained in:
parent
f4bbd53d44
commit
fae965fa32
10 changed files with 165 additions and 45 deletions
|
|
@ -33,11 +33,12 @@ val Deps = new {
|
||||||
}
|
}
|
||||||
|
|
||||||
val Gs = new {
|
val Gs = new {
|
||||||
val Datagen: ModuleID = "gs" %% "gs-datagen-core-v0" % "0.1.1"
|
val Datagen: ModuleID = "gs" %% "gs-datagen-core-v0" % "0.4.1"
|
||||||
}
|
}
|
||||||
|
|
||||||
val BouncyCastle = new {
|
val BouncyCastle = new {
|
||||||
val Provider: ModuleID = "org.bouncycastle" % "bcprov-jdk18on" % "1.83"
|
val Provider: ModuleID = "org.bouncycastle" % "bcprov-jdk18on" % "1.83"
|
||||||
|
val PKIX: ModuleID = "org.bouncycastle" % "bcpkix-jdk18on" % "1.83"
|
||||||
}
|
}
|
||||||
|
|
||||||
val MUnit: ModuleID = "org.scalameta" %% "munit" % "1.2.2"
|
val MUnit: ModuleID = "org.scalameta" %% "munit" % "1.2.2"
|
||||||
|
|
@ -65,7 +66,8 @@ lazy val core = project
|
||||||
)
|
)
|
||||||
.settings(
|
.settings(
|
||||||
libraryDependencies ++= Seq(
|
libraryDependencies ++= Seq(
|
||||||
Deps.Cats.Effect
|
Deps.Cats.Effect,
|
||||||
|
Deps.BouncyCastle.PKIX
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,39 @@
|
||||||
package gs.crypto.v0
|
package gs.crypto.v0
|
||||||
|
|
||||||
|
import cats.effect.Sync
|
||||||
|
import cats.syntax.all.*
|
||||||
|
import java.security.PrivateKey
|
||||||
|
import java.security.PublicKey
|
||||||
|
|
||||||
/** Base implementation for [[KeyLoader]] that provides standard parsing
|
/** Base implementation for [[KeyLoader]] that provides standard parsing
|
||||||
* functionality.
|
* functionality.
|
||||||
*/
|
*/
|
||||||
abstract class BaseKeyLoader[F[_]] extends KeyLoader[F]:
|
abstract class BaseKeyLoader[F[_]: Sync] extends KeyLoader[F]:
|
||||||
/** @return
|
|
||||||
* The key loader configuration.
|
def loadPublicKey(
|
||||||
*/
|
location: String,
|
||||||
|
algorithm: String
|
||||||
|
): F[Either[KeyLoadError, PublicKey]] =
|
||||||
|
loadKey(location).flatMap {
|
||||||
|
case Left(err) => Sync[F].pure(Left(err))
|
||||||
|
case Right(key) =>
|
||||||
|
KeyLoader.loadPublicKey[F](key, algorithm).map(Right(_))
|
||||||
|
}
|
||||||
|
|
||||||
|
def loadPrivateKey(
|
||||||
|
location: String,
|
||||||
|
algorithm: String
|
||||||
|
): F[Either[KeyLoadError, PrivateKey]] =
|
||||||
|
loadKey(location).flatMap {
|
||||||
|
case Left(err) => Sync[F].pure(Left(err))
|
||||||
|
case Right(key) =>
|
||||||
|
KeyLoader.loadPrivateKey[F](key, algorithm).map(Right(_))
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @return
|
||||||
|
* The key loader configuration.
|
||||||
|
*/
|
||||||
|
|
||||||
def config: BaseKeyLoader.Config
|
def config: BaseKeyLoader.Config
|
||||||
|
|
||||||
protected def prepareKey(base: String): Either[KeyLoadError, Array[Byte]] =
|
protected def prepareKey(base: String): Either[KeyLoadError, Array[Byte]] =
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@ package gs.crypto.v0
|
||||||
|
|
||||||
import cats.effect.Sync
|
import cats.effect.Sync
|
||||||
import cats.syntax.all.*
|
import cats.syntax.all.*
|
||||||
import java.nio.charset.Charset
|
|
||||||
import java.nio.file.Files
|
import java.nio.file.Files
|
||||||
import java.nio.file.Path
|
import java.nio.file.Path
|
||||||
|
|
||||||
|
|
@ -14,8 +13,7 @@ import java.nio.file.Path
|
||||||
* The expected character set for the loaded file.
|
* The expected character set for the loaded file.
|
||||||
*/
|
*/
|
||||||
class FileKeyLoader[F[_]: Sync](
|
class FileKeyLoader[F[_]: Sync](
|
||||||
val config: BaseKeyLoader.Config,
|
val config: BaseKeyLoader.Config
|
||||||
val charset: Charset
|
|
||||||
) extends BaseKeyLoader[F]:
|
) extends BaseKeyLoader[F]:
|
||||||
|
|
||||||
/** @inheritDocs
|
/** @inheritDocs
|
||||||
|
|
@ -28,5 +26,5 @@ class FileKeyLoader[F[_]: Sync](
|
||||||
case false =>
|
case false =>
|
||||||
Sync[F].pure(Left(KeyLoadError.PathNotRegularFile(p, location)))
|
Sync[F].pure(Left(KeyLoadError.PathNotRegularFile(p, location)))
|
||||||
case true =>
|
case true =>
|
||||||
Sync[F].delay(Files.readString(p, charset)).map(prepareKey)
|
Sync[F].delay(Files.readString(p)).map(prepareKey)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,38 +1,61 @@
|
||||||
package gs.crypto.v0
|
package gs.crypto.v0
|
||||||
|
|
||||||
import cats.Applicative
|
|
||||||
import cats.effect.Sync
|
import cats.effect.Sync
|
||||||
import java.nio.charset.Charset
|
import java.security.KeyFactory
|
||||||
import java.nio.charset.StandardCharsets
|
import java.security.PrivateKey
|
||||||
|
import java.security.PublicKey
|
||||||
|
import java.security.spec.PKCS8EncodedKeySpec
|
||||||
|
import java.security.spec.X509EncodedKeySpec
|
||||||
|
|
||||||
/** Interface for loading keys (e.g. public/private keys) into memory.
|
/** Interface for loading keys (e.g. public/private keys) into memory.
|
||||||
*/
|
*/
|
||||||
trait KeyLoader[F[_]]:
|
trait KeyLoader[F[_]]:
|
||||||
/** Load the key from the given location.
|
/** Load the key from the given location. This gets the raw bytes, but does
|
||||||
|
* not handle _any_ standard encodings such as PKCS8 or X509. These are
|
||||||
|
* handled by helper functions (static) or [[BaseKeyLoader]].
|
||||||
*
|
*
|
||||||
* @param location
|
* @param location
|
||||||
* The location of the key.
|
* The location of the key.
|
||||||
* @return
|
* @return
|
||||||
* The loaded key.
|
* The key's raw bytes - ready for further decoding.
|
||||||
*/
|
*/
|
||||||
def loadKey(location: String): F[Either[KeyLoadError, Array[Byte]]]
|
def loadKey(location: String): F[Either[KeyLoadError, Array[Byte]]]
|
||||||
|
|
||||||
object KeyLoader:
|
object KeyLoader:
|
||||||
|
|
||||||
def stringLoader[F[_]: Applicative](
|
def stringLoader[F[_]: Sync](
|
||||||
config: BaseKeyLoader.Config
|
config: BaseKeyLoader.Config = BaseKeyLoader.Defaults.Config
|
||||||
): StringKeyLoader[F] =
|
): StringKeyLoader[F] =
|
||||||
new StringKeyLoader[F](config)
|
new StringKeyLoader(config)
|
||||||
|
|
||||||
def fileLoader[F[_]: Sync](
|
def fileLoader[F[_]: Sync](
|
||||||
config: BaseKeyLoader.Config,
|
config: BaseKeyLoader.Config = BaseKeyLoader.Defaults.Config
|
||||||
charset: Charset = StandardCharsets.UTF_8
|
|
||||||
): FileKeyLoader[F] =
|
): FileKeyLoader[F] =
|
||||||
new FileKeyLoader(config, charset)
|
new FileKeyLoader(config)
|
||||||
|
|
||||||
def resourceLoader[F[_]: Sync](
|
def resourceLoader[F[_]: Sync](
|
||||||
config: BaseKeyLoader.Config
|
config: BaseKeyLoader.Config = BaseKeyLoader.Defaults.Config
|
||||||
): ResourceKeyLoader[F] =
|
): ResourceKeyLoader[F] =
|
||||||
new ResourceKeyLoader(config)
|
new ResourceKeyLoader(config)
|
||||||
|
|
||||||
|
def loadPublicKey[F[_]: Sync](
|
||||||
|
publicKeyRawBytes: Array[Byte],
|
||||||
|
algorithm: String
|
||||||
|
): F[PublicKey] =
|
||||||
|
Sync[F].delay {
|
||||||
|
val spec = new X509EncodedKeySpec(publicKeyRawBytes)
|
||||||
|
val keyFactory = KeyFactory.getInstance(algorithm)
|
||||||
|
keyFactory.generatePublic(spec)
|
||||||
|
}
|
||||||
|
|
||||||
|
def loadPrivateKey[F[_]: Sync](
|
||||||
|
privateKeyRawBytes: Array[Byte],
|
||||||
|
algorithm: String
|
||||||
|
): F[PrivateKey] =
|
||||||
|
Sync[F].delay {
|
||||||
|
val spec = new PKCS8EncodedKeySpec(privateKeyRawBytes)
|
||||||
|
val keyFactory = KeyFactory.getInstance(algorithm)
|
||||||
|
keyFactory.generatePrivate(spec)
|
||||||
|
}
|
||||||
|
|
||||||
end KeyLoader
|
end KeyLoader
|
||||||
|
|
|
||||||
|
|
@ -3,4 +3,8 @@ package gs.crypto.v0
|
||||||
/** Used to verify cryptographic signatures.
|
/** Used to verify cryptographic signatures.
|
||||||
*/
|
*/
|
||||||
trait SignatureVerifier[F[_]]:
|
trait SignatureVerifier[F[_]]:
|
||||||
def verify(signature: Signature): F[SignatureValidity]
|
|
||||||
|
def verify(
|
||||||
|
signature: Signature,
|
||||||
|
data: Array[Byte]
|
||||||
|
): F[SignatureValidity]
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,15 @@
|
||||||
package gs.crypto.v0
|
package gs.crypto.v0
|
||||||
|
|
||||||
import cats.Applicative
|
import cats.effect.Sync
|
||||||
|
|
||||||
/** Implementation of [[KeyLoader]] that loads keys directly from input strings.
|
/** Implementation of [[KeyLoader]] that loads keys directly from input strings.
|
||||||
*
|
*
|
||||||
* @param config
|
* @param config
|
||||||
* The key loader configuration.
|
* The key loader configuration.
|
||||||
*/
|
*/
|
||||||
class StringKeyLoader[F[_]: Applicative](
|
class StringKeyLoader[F[_]: Sync](
|
||||||
val config: BaseKeyLoader.Config
|
val config: BaseKeyLoader.Config
|
||||||
) extends BaseKeyLoader[F]:
|
) extends BaseKeyLoader[F]:
|
||||||
|
|
||||||
/** @inheritDocs
|
override def loadKey(location: String): F[Either[KeyLoadError, Array[Byte]]] =
|
||||||
*/
|
Sync[F].delay(prepareKey(location))
|
||||||
override def loadKey(
|
|
||||||
location: String
|
|
||||||
): F[Either[KeyLoadError, Array[Byte]]] =
|
|
||||||
Applicative[F].pure(prepareKey(location))
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
package gs.crypto.v0.eddsa
|
||||||
|
|
||||||
|
object Ed25519:
|
||||||
|
|
||||||
|
val Algorithm: String = "Ed25519"
|
||||||
|
|
||||||
|
end Ed25519
|
||||||
|
|
@ -5,7 +5,7 @@ import gs.crypto.v0.Signature
|
||||||
import gs.crypto.v0.Signer
|
import gs.crypto.v0.Signer
|
||||||
import java.nio.charset.Charset
|
import java.nio.charset.Charset
|
||||||
import java.nio.charset.StandardCharsets
|
import java.nio.charset.StandardCharsets
|
||||||
import org.bouncycastle.crypto.params.Ed25519PrivateKeyParameters
|
import java.security.PrivateKey
|
||||||
|
|
||||||
/** Implementation of [[Signer]] that uses the Ed25519 system to calculate
|
/** Implementation of [[Signer]] that uses the Ed25519 system to calculate
|
||||||
* signatures.
|
* signatures.
|
||||||
|
|
@ -16,18 +16,17 @@ import org.bouncycastle.crypto.params.Ed25519PrivateKeyParameters
|
||||||
* The private key.
|
* The private key.
|
||||||
*/
|
*/
|
||||||
final class Ed25519Signer[F[_]: Sync](
|
final class Ed25519Signer[F[_]: Sync](
|
||||||
privateKey: Array[Byte]
|
privateKey: PrivateKey
|
||||||
) extends Signer[F]:
|
) extends Signer[F]:
|
||||||
private lazy val params = new Ed25519PrivateKeyParameters(privateKey)
|
|
||||||
|
|
||||||
/** @inheritDocs
|
/** @inheritDocs
|
||||||
*/
|
*/
|
||||||
override def sign(data: Array[Byte]): F[Signature] =
|
override def sign(data: Array[Byte]): F[Signature] =
|
||||||
Sync[F].delay {
|
Sync[F].delay {
|
||||||
val s = new org.bouncycastle.crypto.signers.Ed25519Signer()
|
val s = java.security.Signature.getInstance(Ed25519.Algorithm)
|
||||||
val _ = s.init(true, params)
|
val _ = s.initSign(privateKey)
|
||||||
val _ = s.update(data, 0, data.length)
|
val _ = s.update(data)
|
||||||
Signature(s.generateSignature())
|
Signature(s.sign())
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @inheritDocs
|
/** @inheritDocs
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ import cats.effect.Sync
|
||||||
import gs.crypto.v0.Signature
|
import gs.crypto.v0.Signature
|
||||||
import gs.crypto.v0.SignatureValidity
|
import gs.crypto.v0.SignatureValidity
|
||||||
import gs.crypto.v0.SignatureVerifier
|
import gs.crypto.v0.SignatureVerifier
|
||||||
import org.bouncycastle.crypto.params.Ed25519PublicKeyParameters
|
import java.security.PublicKey
|
||||||
|
|
||||||
/** Implementation of [[SignatureVerifier]] that uses the Ed25519 system to
|
/** Implementation of [[SignatureVerifier]] that uses the Ed25519 system to
|
||||||
* verify signatures.
|
* verify signatures.
|
||||||
|
|
@ -15,15 +15,18 @@ import org.bouncycastle.crypto.params.Ed25519PublicKeyParameters
|
||||||
* The public key.
|
* The public key.
|
||||||
*/
|
*/
|
||||||
final class Ed25519Verifier[F[_]: Sync](
|
final class Ed25519Verifier[F[_]: Sync](
|
||||||
publicKey: Array[Byte]
|
publicKey: PublicKey
|
||||||
) extends SignatureVerifier[F]:
|
) extends SignatureVerifier[F]:
|
||||||
private lazy val params = new Ed25519PublicKeyParameters(publicKey)
|
|
||||||
|
|
||||||
/** @inheritDocs
|
/** @inheritDocs
|
||||||
*/
|
*/
|
||||||
override def verify(signature: Signature): F[SignatureValidity] =
|
override def verify(
|
||||||
|
signature: Signature,
|
||||||
|
data: Array[Byte] // TODO: CLEAN UP
|
||||||
|
): F[SignatureValidity] =
|
||||||
Sync[F].delay {
|
Sync[F].delay {
|
||||||
val s = new org.bouncycastle.crypto.signers.Ed25519Signer()
|
val s = java.security.Signature.getInstance(Ed25519.Algorithm)
|
||||||
val _ = s.init(false, params)
|
val _ = s.initVerify(publicKey)
|
||||||
SignatureValidity(s.verifySignature(signature.unwrap()))
|
val _ = s.update(data)
|
||||||
|
SignatureValidity(s.verify(signature.unwrap()))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,61 @@
|
||||||
|
package gs.crypto.v0.eddsa
|
||||||
|
|
||||||
|
import cats.effect.IO
|
||||||
|
import cats.effect.unsafe.IORuntime
|
||||||
|
import gs.crypto.v0.KeyLoader
|
||||||
|
import gs.crypto.v0.SignatureValidity
|
||||||
|
import java.security.PrivateKey
|
||||||
|
import java.security.PublicKey
|
||||||
|
import java.util.UUID
|
||||||
|
import munit.Location
|
||||||
|
|
||||||
|
class Ed25519Tests extends munit.FunSuite:
|
||||||
|
import Ed25519Tests.Resources
|
||||||
|
|
||||||
|
given IORuntime = IORuntime.global
|
||||||
|
|
||||||
|
def iotest(
|
||||||
|
name: String
|
||||||
|
)(
|
||||||
|
f: => IO[Unit]
|
||||||
|
)(
|
||||||
|
using
|
||||||
|
Location
|
||||||
|
): Unit =
|
||||||
|
test(name)(f.unsafeRunSync())
|
||||||
|
|
||||||
|
val keyLoader = KeyLoader.resourceLoader[IO]()
|
||||||
|
|
||||||
|
iotest(
|
||||||
|
"should sign some data and verify that signature"
|
||||||
|
) {
|
||||||
|
val data = UUID.randomUUID().toString()
|
||||||
|
loadKeysOrFail().flatMap { case (publicKey, privateKey) =>
|
||||||
|
val signer = new Ed25519Signer[IO](privateKey)
|
||||||
|
val verifier = new Ed25519Verifier[IO](publicKey)
|
||||||
|
|
||||||
|
for
|
||||||
|
signature <- signer.sign(data)
|
||||||
|
validity <- verifier.verify(signature, data.getBytes())
|
||||||
|
yield assertEquals(validity, SignatureValidity.Valid)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private def loadKeysOrFail(): IO[(PublicKey, PrivateKey)] =
|
||||||
|
keyLoader.loadPublicKey(Resources.PublicKey, Ed25519.Algorithm).flatMap {
|
||||||
|
case Left(_) => fail("Failed to load public key.")
|
||||||
|
case Right(pub) =>
|
||||||
|
keyLoader.loadPrivateKey(Resources.PrivateKey, Ed25519.Algorithm).map {
|
||||||
|
case Left(_) => fail("Failed to load private key.")
|
||||||
|
case Right(priv) => (pub, priv)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
object Ed25519Tests:
|
||||||
|
|
||||||
|
object Resources:
|
||||||
|
val PublicKey: String = "public.pem"
|
||||||
|
val PrivateKey: String = "private.pem"
|
||||||
|
end Resources
|
||||||
|
|
||||||
|
end Ed25519Tests
|
||||||
Loading…
Add table
Reference in a new issue