Compare commits
No commits in common. "main" and "0.1.1" have entirely different histories.
33 changed files with 412 additions and 622 deletions
35
build.sbt
35
build.sbt
|
|
@ -1,4 +1,4 @@
|
||||||
val scala3: String = "3.8.2"
|
val scala3: String = "3.8.1"
|
||||||
|
|
||||||
ThisBuild / scalaVersion := scala3
|
ThisBuild / scalaVersion := scala3
|
||||||
ThisBuild / versionScheme := Some("semver-spec")
|
ThisBuild / versionScheme := Some("semver-spec")
|
||||||
|
|
@ -33,19 +33,11 @@ val Deps = new {
|
||||||
}
|
}
|
||||||
|
|
||||||
val Gs = new {
|
val Gs = new {
|
||||||
val Std = new {
|
val Datagen: ModuleID = "gs" %% "gs-datagen-core-v0" % "0.1.1"
|
||||||
val Version: String = "0.1.2"
|
|
||||||
|
|
||||||
val Core: ModuleID = "gs" %% "gs-std-core-v0" % Version
|
|
||||||
val IO: ModuleID = "gs" %% "gs-std-io-v0" % Version
|
|
||||||
val Effect: ModuleID = "gs" %% "gs-std-effect-v0" % Version
|
|
||||||
}
|
|
||||||
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"
|
||||||
|
|
@ -60,7 +52,7 @@ lazy val testSettings = Seq(
|
||||||
|
|
||||||
lazy val `gs-crypto` = project
|
lazy val `gs-crypto` = project
|
||||||
.in(file("."))
|
.in(file("."))
|
||||||
.aggregate(core, argon2, rsa, eddsa)
|
.aggregate(core, argon2, rsa)
|
||||||
.settings(noPublishSettings)
|
.settings(noPublishSettings)
|
||||||
.settings(name := s"${gsProjectName.value}-v${semVerMajor.value}")
|
.settings(name := s"${gsProjectName.value}-v${semVerMajor.value}")
|
||||||
|
|
||||||
|
|
@ -73,11 +65,7 @@ lazy val core = project
|
||||||
)
|
)
|
||||||
.settings(
|
.settings(
|
||||||
libraryDependencies ++= Seq(
|
libraryDependencies ++= Seq(
|
||||||
Deps.Gs.Std.Core,
|
Deps.Cats.Effect
|
||||||
Deps.Gs.Std.IO,
|
|
||||||
Deps.Gs.Std.Effect,
|
|
||||||
Deps.Cats.Effect,
|
|
||||||
Deps.BouncyCastle.PKIX
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -109,18 +97,3 @@ lazy val rsa = project
|
||||||
Deps.Cats.Effect
|
Deps.Cats.Effect
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
lazy val eddsa = project
|
|
||||||
.in(file("modules/eddsa"))
|
|
||||||
.dependsOn(core)
|
|
||||||
.settings(sharedSettings)
|
|
||||||
.settings(testSettings)
|
|
||||||
.settings(
|
|
||||||
name := s"${gsProjectName.value}-eddsa-v${semVerMajor.value}"
|
|
||||||
)
|
|
||||||
.settings(
|
|
||||||
libraryDependencies ++= Seq(
|
|
||||||
Deps.Cats.Effect,
|
|
||||||
Deps.BouncyCastle.Provider
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ package gs.crypto.v0.argon2
|
||||||
|
|
||||||
import cats.effect.Sync
|
import cats.effect.Sync
|
||||||
import cats.syntax.all.*
|
import cats.syntax.all.*
|
||||||
import gs.std.v0.effect.Rng
|
import gs.crypto.v0.RandomByteProvider
|
||||||
import java.nio.charset.Charset
|
import java.nio.charset.Charset
|
||||||
import java.nio.charset.StandardCharsets
|
import java.nio.charset.StandardCharsets
|
||||||
import org.bouncycastle.crypto.generators.Argon2BytesGenerator
|
import org.bouncycastle.crypto.generators.Argon2BytesGenerator
|
||||||
|
|
@ -20,7 +20,7 @@ import org.bouncycastle.crypto.params.Argon2Parameters
|
||||||
final class Argon2[F[_]: Sync](
|
final class Argon2[F[_]: Sync](
|
||||||
val config: Argon2.Config,
|
val config: Argon2.Config,
|
||||||
val secret: Argon2Secret,
|
val secret: Argon2Secret,
|
||||||
val rng: Rng[F]
|
val randomByteProvider: RandomByteProvider[F]
|
||||||
):
|
):
|
||||||
|
|
||||||
/** Calculate a new hash for some input.
|
/** Calculate a new hash for some input.
|
||||||
|
|
@ -82,7 +82,7 @@ final class Argon2[F[_]: Sync](
|
||||||
}
|
}
|
||||||
|
|
||||||
private def randomSalt(): F[Array[Byte]] =
|
private def randomSalt(): F[Array[Byte]] =
|
||||||
rng.nextByteArray(config.saltLengthInBytes)
|
randomByteProvider.generateBytes(config.saltLengthInBytes)
|
||||||
|
|
||||||
private def buildAlgorithmParams(salt: Array[Byte]): Argon2Parameters =
|
private def buildAlgorithmParams(salt: Array[Byte]): Argon2Parameters =
|
||||||
new Argon2Parameters.Builder(config.algorithmType)
|
new Argon2Parameters.Builder(config.algorithmType)
|
||||||
|
|
@ -137,7 +137,7 @@ object Argon2:
|
||||||
)
|
)
|
||||||
|
|
||||||
/** @return
|
/** @return
|
||||||
* [[Argon2.Config]] with default settings. Suitable for most cases.
|
* [[Argon2.Params]] with default settings. Suitable for most cases.
|
||||||
*/
|
*/
|
||||||
def defaultConfig(): Config =
|
def defaultConfig(): Config =
|
||||||
Config(
|
Config(
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
package gs.crypto.v0.argon2
|
package gs.crypto.v0.argon2
|
||||||
|
|
||||||
import gs.std.v0.core.B64
|
import gs.crypto.v0.B64
|
||||||
import gs.std.v0.core.Base64Encoder
|
import gs.crypto.v0.Base64Encoder
|
||||||
import java.util.Base64
|
import java.util.Base64
|
||||||
import java.util.Objects
|
import java.util.Objects
|
||||||
import scala.util.Try
|
import scala.util.Try
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ package gs.crypto.v0.argon2
|
||||||
import cats.Applicative
|
import cats.Applicative
|
||||||
import cats.effect.Sync
|
import cats.effect.Sync
|
||||||
import cats.syntax.all.*
|
import cats.syntax.all.*
|
||||||
import gs.std.v0.effect.Rng
|
import gs.crypto.v0.RandomByteProvider
|
||||||
import java.nio.charset.StandardCharsets
|
import java.nio.charset.StandardCharsets
|
||||||
import java.nio.file.Files
|
import java.nio.file.Files
|
||||||
import java.nio.file.Path
|
import java.nio.file.Path
|
||||||
|
|
@ -94,15 +94,15 @@ object Argon2Secret:
|
||||||
*
|
*
|
||||||
* @param size
|
* @param size
|
||||||
* The number of bytes in the secret value.
|
* The number of bytes in the secret value.
|
||||||
* @param rng
|
* @param randomByteProvider
|
||||||
* The random number generator.
|
* The [[RandomByteProvider]].
|
||||||
* @return
|
* @return
|
||||||
* The new [[Argon2Secret]].
|
* The new [[Argon2Secret]].
|
||||||
*/
|
*/
|
||||||
def generate[F[_]: Applicative](
|
def generate[F[_]: Applicative](
|
||||||
size: Int,
|
size: Int,
|
||||||
rng: Rng[F]
|
randomByteProvider: RandomByteProvider[F]
|
||||||
): F[Argon2Secret] =
|
): F[Argon2Secret] =
|
||||||
rng.nextByteArray(size).map(new Argon2Secret(_))
|
randomByteProvider.generateBytes(size).map(new Argon2Secret(_))
|
||||||
|
|
||||||
end Argon2Secret
|
end Argon2Secret
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ package gs.crypto.v0.argon2
|
||||||
|
|
||||||
import cats.effect.IO
|
import cats.effect.IO
|
||||||
import cats.effect.unsafe.IORuntime
|
import cats.effect.unsafe.IORuntime
|
||||||
import gs.std.v0.effect.Rng
|
import gs.crypto.v0.RandomByteProvider
|
||||||
import munit.Location
|
import munit.Location
|
||||||
import org.bouncycastle.crypto.params.Argon2Parameters
|
import org.bouncycastle.crypto.params.Argon2Parameters
|
||||||
|
|
||||||
|
|
@ -19,7 +19,7 @@ class Argon2Tests extends munit.FunSuite:
|
||||||
): Unit =
|
): Unit =
|
||||||
test(name)(f.unsafeRunSync())
|
test(name)(f.unsafeRunSync())
|
||||||
|
|
||||||
val rng: Rng[IO] = Rng.secure[IO]
|
val rng: RandomByteProvider[IO] = RandomByteProvider.secureRandom[IO]
|
||||||
|
|
||||||
val altConfig: Argon2.Config = Argon2.Config(
|
val altConfig: Argon2.Config = Argon2.Config(
|
||||||
algorithmVersion = Argon2Parameters.ARGON2_VERSION_10,
|
algorithmVersion = Argon2Parameters.ARGON2_VERSION_10,
|
||||||
|
|
|
||||||
23
modules/core/src/main/scala/gs/crypto/v0/Base64Decoder.scala
Normal file
23
modules/core/src/main/scala/gs/crypto/v0/Base64Decoder.scala
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
package gs.crypto.v0
|
||||||
|
|
||||||
|
import java.{util => ju}
|
||||||
|
|
||||||
|
/** Implementation of [[Decoder]] for Base64 strings.
|
||||||
|
*/
|
||||||
|
object Base64Decoder extends Decoder[B64]:
|
||||||
|
private lazy val d: ju.Base64.Decoder = ju.Base64.getDecoder()
|
||||||
|
|
||||||
|
/** @inheritDocs
|
||||||
|
*/
|
||||||
|
override def decode(input: B64): Array[Byte] =
|
||||||
|
d.decode(input.data)
|
||||||
|
|
||||||
|
/** Decode some arbitrary string data.
|
||||||
|
*
|
||||||
|
* @param input
|
||||||
|
* The data to decode.
|
||||||
|
* @return
|
||||||
|
* The decoded bytes.
|
||||||
|
*/
|
||||||
|
def decodeUnsafe(input: String): Array[Byte] =
|
||||||
|
d.decode(input)
|
||||||
23
modules/core/src/main/scala/gs/crypto/v0/Base64Encoder.scala
Normal file
23
modules/core/src/main/scala/gs/crypto/v0/Base64Encoder.scala
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
package gs.crypto.v0
|
||||||
|
|
||||||
|
import java.nio.charset.Charset
|
||||||
|
import java.nio.charset.StandardCharsets
|
||||||
|
import java.util.Base64
|
||||||
|
|
||||||
|
/** Implementation of [[Encoder]] for Base64.
|
||||||
|
*/
|
||||||
|
object Base64Encoder extends Encoder[B64]:
|
||||||
|
private lazy val e: Base64.Encoder = Base64.getEncoder()
|
||||||
|
|
||||||
|
/** @inheritDocs
|
||||||
|
*/
|
||||||
|
override def encode(input: Array[Byte]): B64 =
|
||||||
|
B64(e.encodeToString(input))
|
||||||
|
|
||||||
|
/** @inheritDocs
|
||||||
|
*/
|
||||||
|
override def encode(
|
||||||
|
input: String,
|
||||||
|
charset: Charset = StandardCharsets.UTF_8
|
||||||
|
): B64 =
|
||||||
|
encode(input.getBytes(charset))
|
||||||
|
|
@ -1,126 +0,0 @@
|
||||||
package gs.crypto.v0
|
|
||||||
|
|
||||||
import cats.effect.Sync
|
|
||||||
import cats.syntax.all.*
|
|
||||||
import gs.std.v0.core.B64
|
|
||||||
import gs.std.v0.core.Base64Decoder
|
|
||||||
import gs.std.v0.core.Decoder
|
|
||||||
import java.security.PrivateKey
|
|
||||||
import java.security.PublicKey
|
|
||||||
|
|
||||||
/** Base implementation for [[KeyLoader]] that provides standard parsing
|
|
||||||
* functionality.
|
|
||||||
*/
|
|
||||||
abstract class BaseKeyLoader[F[_]: Sync] extends KeyLoader[F]:
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
protected def prepareKey(base: String): Either[KeyLoadError, Array[Byte]] =
|
|
||||||
scala.util
|
|
||||||
.Try {
|
|
||||||
val staged = trim(collapse(unwrap(base)))
|
|
||||||
config.decoder.decode(B64(staged))
|
|
||||||
}
|
|
||||||
.toEither
|
|
||||||
.left
|
|
||||||
.map(_ => KeyLoadError.DecodingFailure(config.encodingName))
|
|
||||||
|
|
||||||
private def unwrap(base: String): String =
|
|
||||||
config.wrappers.foldLeft(base) {
|
|
||||||
(
|
|
||||||
acc,
|
|
||||||
w
|
|
||||||
) => acc.replace(w, "")
|
|
||||||
}
|
|
||||||
|
|
||||||
private def collapse(base: String): String =
|
|
||||||
if config.shouldCollapse then base.replace("\n", "") else base
|
|
||||||
|
|
||||||
private def trim(base: String): String =
|
|
||||||
if config.shouldTrim then base.trim() else base
|
|
||||||
|
|
||||||
object BaseKeyLoader:
|
|
||||||
|
|
||||||
/** Configuration for loading keys.
|
|
||||||
*
|
|
||||||
* @param wrappers
|
|
||||||
* Values that wrap keys and must be removed.
|
|
||||||
* @param shouldCollapse
|
|
||||||
* Whether newlines should be collapsed.
|
|
||||||
* @param shouldTrim
|
|
||||||
* Whether the string data should be trimmed.
|
|
||||||
* @param encodingName
|
|
||||||
* The name of the encoding.
|
|
||||||
* @param decoder
|
|
||||||
* The decoder to be used on the pre-processed input.
|
|
||||||
*/
|
|
||||||
case class Config(
|
|
||||||
wrappers: List[String],
|
|
||||||
shouldCollapse: Boolean,
|
|
||||||
shouldTrim: Boolean,
|
|
||||||
encodingName: String,
|
|
||||||
decoder: Decoder[B64]
|
|
||||||
)
|
|
||||||
|
|
||||||
/** Default configuration values that work in most cases.
|
|
||||||
*/
|
|
||||||
object Defaults:
|
|
||||||
|
|
||||||
/** By default, generic begin/end public/private key wrappers are included.
|
|
||||||
*/
|
|
||||||
val Wrappers: List[String] = List(
|
|
||||||
"-----BEGIN PUBLIC KEY-----",
|
|
||||||
"-----END PUBLIC KEY-----",
|
|
||||||
"-----BEGIN PRIVATE KEY-----",
|
|
||||||
"-----END PRIVATE KEY-----"
|
|
||||||
)
|
|
||||||
|
|
||||||
/** By default, stringified key data has newlines removed.
|
|
||||||
*/
|
|
||||||
val ShouldCollapse: Boolean = true
|
|
||||||
|
|
||||||
/** By default, stringified key data is trimmed.
|
|
||||||
*/
|
|
||||||
val ShouldTrim: Boolean = true
|
|
||||||
|
|
||||||
/** The default encoding name is 'base64'.
|
|
||||||
*/
|
|
||||||
val EncodingName: String = "base64"
|
|
||||||
|
|
||||||
/** The default configuration handles standard wrapped, base64-encoded keys.
|
|
||||||
*/
|
|
||||||
val Config: BaseKeyLoader.Config = BaseKeyLoader.Config(
|
|
||||||
wrappers = Wrappers,
|
|
||||||
shouldCollapse = ShouldCollapse,
|
|
||||||
shouldTrim = ShouldTrim,
|
|
||||||
encodingName = EncodingName,
|
|
||||||
decoder = s => Base64Decoder.decode(s)
|
|
||||||
)
|
|
||||||
|
|
||||||
end Defaults
|
|
||||||
|
|
||||||
end BaseKeyLoader
|
|
||||||
4
modules/core/src/main/scala/gs/crypto/v0/Decoder.scala
Normal file
4
modules/core/src/main/scala/gs/crypto/v0/Decoder.scala
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
package gs.crypto.v0
|
||||||
|
|
||||||
|
trait Decoder[A]:
|
||||||
|
def decode(input: A): Array[Byte]
|
||||||
58
modules/core/src/main/scala/gs/crypto/v0/Encode.scala
Normal file
58
modules/core/src/main/scala/gs/crypto/v0/Encode.scala
Normal file
|
|
@ -0,0 +1,58 @@
|
||||||
|
package gs.crypto.v0
|
||||||
|
|
||||||
|
import java.nio.charset.Charset
|
||||||
|
import java.nio.charset.StandardCharsets
|
||||||
|
|
||||||
|
/** Helper functions for encoding data.
|
||||||
|
*/
|
||||||
|
object Encode:
|
||||||
|
|
||||||
|
/** Encode an array of bytes using base64.
|
||||||
|
*
|
||||||
|
* @param input
|
||||||
|
* The bytes to encode.
|
||||||
|
* @return
|
||||||
|
* The base64 string.
|
||||||
|
*/
|
||||||
|
def base64(input: Array[Byte]): B64 = Base64Encoder.encode(input)
|
||||||
|
|
||||||
|
/** Encode a string using base64.
|
||||||
|
*
|
||||||
|
* @param input
|
||||||
|
* The string to encode.
|
||||||
|
* @param charset
|
||||||
|
* The character set of the input string.
|
||||||
|
* @return
|
||||||
|
* The base64 string.
|
||||||
|
*/
|
||||||
|
def base64(
|
||||||
|
input: String,
|
||||||
|
charset: Charset = StandardCharsets.UTF_8
|
||||||
|
): B64 =
|
||||||
|
Base64Encoder.encode(input, charset)
|
||||||
|
|
||||||
|
/** Encode an array of bytes using hexadecimal.
|
||||||
|
*
|
||||||
|
* @param input
|
||||||
|
* The bytes to encode.
|
||||||
|
* @return
|
||||||
|
* The hexadecimal string.
|
||||||
|
*/
|
||||||
|
def hex(input: Array[Byte]): Hex = HexEncoder.encode(input)
|
||||||
|
|
||||||
|
/** Encode a string using hexadecimal.
|
||||||
|
*
|
||||||
|
* @param input
|
||||||
|
* The string to encode.
|
||||||
|
* @param charset
|
||||||
|
* The character set of the input string.
|
||||||
|
* @return
|
||||||
|
* The hexadecimal string.
|
||||||
|
*/
|
||||||
|
def hex(
|
||||||
|
input: String,
|
||||||
|
charset: Charset = StandardCharsets.UTF_8
|
||||||
|
): Hex =
|
||||||
|
HexEncoder.encode(input, charset)
|
||||||
|
|
||||||
|
end Encode
|
||||||
109
modules/core/src/main/scala/gs/crypto/v0/Encoded.scala
Normal file
109
modules/core/src/main/scala/gs/crypto/v0/Encoded.scala
Normal file
|
|
@ -0,0 +1,109 @@
|
||||||
|
package gs.crypto.v0
|
||||||
|
|
||||||
|
/** Represents encoded data.
|
||||||
|
*
|
||||||
|
* See:
|
||||||
|
* - [[B64]]
|
||||||
|
* - [[Hex]]
|
||||||
|
*/
|
||||||
|
trait Encoded:
|
||||||
|
/** @return
|
||||||
|
* The encoded data (expressed as a string).
|
||||||
|
*/
|
||||||
|
def data: String
|
||||||
|
|
||||||
|
/** @return
|
||||||
|
* Decode the data to a byte array.
|
||||||
|
*/
|
||||||
|
def decode(): Array[Byte]
|
||||||
|
|
||||||
|
/** Represents Base64-encoded data.
|
||||||
|
*
|
||||||
|
* @param data
|
||||||
|
* The encoded data.
|
||||||
|
*/
|
||||||
|
|
||||||
|
final class B64(
|
||||||
|
val data: String
|
||||||
|
) extends Encoded:
|
||||||
|
/** @inheritDocs
|
||||||
|
*/
|
||||||
|
def decode(): Array[Byte] = Base64Decoder.decode(this)
|
||||||
|
|
||||||
|
/** @inheritDocs
|
||||||
|
*/
|
||||||
|
override def equals(obj: Any): Boolean =
|
||||||
|
obj match
|
||||||
|
case other: B64 => data == other.data
|
||||||
|
|
||||||
|
/** @inheritDocs
|
||||||
|
*/
|
||||||
|
override def hashCode(): Int = data.hashCode()
|
||||||
|
|
||||||
|
/** @inheritDocs
|
||||||
|
*/
|
||||||
|
override def toString(): String = data
|
||||||
|
|
||||||
|
object B64:
|
||||||
|
|
||||||
|
/** Instantiate [[B64]] from the given string.
|
||||||
|
*
|
||||||
|
* This function does NOT validate the input.
|
||||||
|
*
|
||||||
|
* @param data
|
||||||
|
* The encoded data.
|
||||||
|
* @return
|
||||||
|
* The new [[B64]] instance.
|
||||||
|
*/
|
||||||
|
def apply(
|
||||||
|
data: String
|
||||||
|
): B64 = new B64(data)
|
||||||
|
|
||||||
|
given CanEqual[B64, B64] = CanEqual.derived
|
||||||
|
|
||||||
|
end B64
|
||||||
|
|
||||||
|
/** Represents Hex-encoded data.
|
||||||
|
*
|
||||||
|
* @param data
|
||||||
|
* The encoded data.
|
||||||
|
*/
|
||||||
|
final class Hex(
|
||||||
|
val data: String
|
||||||
|
) extends Encoded:
|
||||||
|
/** @inheritDocs
|
||||||
|
*/
|
||||||
|
def decode(): Array[Byte] = HexDecoder.decode(this)
|
||||||
|
|
||||||
|
/** @inheritDocs
|
||||||
|
*/
|
||||||
|
override def equals(obj: Any): Boolean =
|
||||||
|
obj match
|
||||||
|
case other: Hex => data == other.data
|
||||||
|
|
||||||
|
/** @inheritDocs
|
||||||
|
*/
|
||||||
|
override def hashCode(): Int = data.hashCode()
|
||||||
|
|
||||||
|
/** @inheritDocs
|
||||||
|
*/
|
||||||
|
override def toString(): String = data
|
||||||
|
|
||||||
|
object Hex:
|
||||||
|
|
||||||
|
/** Instantiate [[Hex]] from the given string.
|
||||||
|
*
|
||||||
|
* This function does NOT validate the input.
|
||||||
|
*
|
||||||
|
* @param data
|
||||||
|
* The encoded data.
|
||||||
|
* @return
|
||||||
|
* The new [[Hex]] instance.
|
||||||
|
*/
|
||||||
|
def apply(
|
||||||
|
data: String
|
||||||
|
): Hex = new Hex(data)
|
||||||
|
|
||||||
|
given CanEqual[Hex, Hex] = CanEqual.derived
|
||||||
|
|
||||||
|
end Hex
|
||||||
44
modules/core/src/main/scala/gs/crypto/v0/Encoder.scala
Normal file
44
modules/core/src/main/scala/gs/crypto/v0/Encoder.scala
Normal file
|
|
@ -0,0 +1,44 @@
|
||||||
|
package gs.crypto.v0
|
||||||
|
|
||||||
|
import java.nio.charset.Charset
|
||||||
|
import java.nio.charset.StandardCharsets
|
||||||
|
|
||||||
|
/** Interface for byte encoding to String formats.
|
||||||
|
*/
|
||||||
|
trait Encoder[+A <: Encoded]:
|
||||||
|
/** Encode an array of bytes.
|
||||||
|
*
|
||||||
|
* @param input
|
||||||
|
* The bytes to encode.
|
||||||
|
* @return
|
||||||
|
* The encoded string.
|
||||||
|
*/
|
||||||
|
def encode(input: Array[Byte]): A
|
||||||
|
|
||||||
|
/** Encode a string.
|
||||||
|
*
|
||||||
|
* @param input
|
||||||
|
* The string to encode.
|
||||||
|
* @param charset
|
||||||
|
* The character set of the input string.
|
||||||
|
* @return
|
||||||
|
* The encoded string.
|
||||||
|
*/
|
||||||
|
def encode(
|
||||||
|
input: String,
|
||||||
|
charset: Charset = StandardCharsets.UTF_8
|
||||||
|
): A
|
||||||
|
|
||||||
|
object Encoder:
|
||||||
|
|
||||||
|
/** @return
|
||||||
|
* The [[Base64Encoder]], typed to `Encoder[Encoded]`.
|
||||||
|
*/
|
||||||
|
def base64(): Encoder[Encoded] = Base64Encoder
|
||||||
|
|
||||||
|
/** @return
|
||||||
|
* The [[HexEncoder]], typed to `Encoder[Encoded]`.
|
||||||
|
*/
|
||||||
|
def hex(): Encoder[Encoded] = HexEncoder
|
||||||
|
|
||||||
|
end Encoder
|
||||||
|
|
@ -1,30 +0,0 @@
|
||||||
package gs.crypto.v0
|
|
||||||
|
|
||||||
import cats.effect.Sync
|
|
||||||
import cats.syntax.all.*
|
|
||||||
import java.nio.file.Files
|
|
||||||
import java.nio.file.Path
|
|
||||||
|
|
||||||
/** Implementation of [[KeyLoader]] that loads keys from encoded string files.
|
|
||||||
*
|
|
||||||
* @param config
|
|
||||||
* The key loader configuration.
|
|
||||||
* @param charset
|
|
||||||
* The expected character set for the loaded file.
|
|
||||||
*/
|
|
||||||
class FileKeyLoader[F[_]: Sync](
|
|
||||||
val config: BaseKeyLoader.Config
|
|
||||||
) extends BaseKeyLoader[F]:
|
|
||||||
|
|
||||||
/** @inheritDocs
|
|
||||||
*/
|
|
||||||
override def loadKey(
|
|
||||||
location: String
|
|
||||||
): F[Either[KeyLoadError, Array[Byte]]] =
|
|
||||||
val p = Path.of(location)
|
|
||||||
Sync[F].delay(Files.isRegularFile(p)).flatMap {
|
|
||||||
case false =>
|
|
||||||
Sync[F].pure(Left(KeyLoadError.PathNotRegularFile(p, location)))
|
|
||||||
case true =>
|
|
||||||
Sync[F].delay(Files.readString(p)).map(prepareKey)
|
|
||||||
}
|
|
||||||
23
modules/core/src/main/scala/gs/crypto/v0/HexDecoder.scala
Normal file
23
modules/core/src/main/scala/gs/crypto/v0/HexDecoder.scala
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
package gs.crypto.v0
|
||||||
|
|
||||||
|
import java.util.HexFormat
|
||||||
|
|
||||||
|
/** Implementation of [[Decoder]] for Hexadecimal strings.
|
||||||
|
*/
|
||||||
|
object HexDecoder extends Decoder[Hex]:
|
||||||
|
private lazy val h: HexFormat = HexFormat.of()
|
||||||
|
|
||||||
|
/** @inheritDocs
|
||||||
|
*/
|
||||||
|
override def decode(input: Hex): Array[Byte] =
|
||||||
|
h.parseHex(input.data)
|
||||||
|
|
||||||
|
/** Decode some arbitrary string data.
|
||||||
|
*
|
||||||
|
* @param input
|
||||||
|
* The data to decode.
|
||||||
|
* @return
|
||||||
|
* The decoded bytes.
|
||||||
|
*/
|
||||||
|
def decodeUnsafe(input: String): Array[Byte] =
|
||||||
|
h.parseHex(input)
|
||||||
23
modules/core/src/main/scala/gs/crypto/v0/HexEncoder.scala
Normal file
23
modules/core/src/main/scala/gs/crypto/v0/HexEncoder.scala
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
package gs.crypto.v0
|
||||||
|
|
||||||
|
import java.nio.charset.Charset
|
||||||
|
import java.nio.charset.StandardCharsets
|
||||||
|
import java.util.HexFormat
|
||||||
|
|
||||||
|
/** Implementation of [[Encoder]] for Hexadecimal strings.
|
||||||
|
*/
|
||||||
|
object HexEncoder extends Encoder[Hex]:
|
||||||
|
private lazy val h: HexFormat = HexFormat.of()
|
||||||
|
|
||||||
|
/** @inheritDocs
|
||||||
|
*/
|
||||||
|
override def encode(input: Array[Byte]): Hex =
|
||||||
|
Hex(h.formatHex(input))
|
||||||
|
|
||||||
|
/** @inheritDocs
|
||||||
|
*/
|
||||||
|
override def encode(
|
||||||
|
input: String,
|
||||||
|
charset: Charset = StandardCharsets.UTF_8
|
||||||
|
): Hex =
|
||||||
|
encode(input.getBytes(charset))
|
||||||
|
|
@ -1,16 +0,0 @@
|
||||||
package gs.crypto.v0
|
|
||||||
|
|
||||||
import java.nio.file.Path
|
|
||||||
|
|
||||||
sealed trait KeyLoadError
|
|
||||||
|
|
||||||
object KeyLoadError:
|
|
||||||
|
|
||||||
case class PathNotRegularFile(
|
|
||||||
path: Path,
|
|
||||||
location: String
|
|
||||||
) extends KeyLoadError
|
|
||||||
|
|
||||||
case class DecodingFailure(encodedFormat: String) extends KeyLoadError
|
|
||||||
|
|
||||||
end KeyLoadError
|
|
||||||
|
|
@ -1,61 +0,0 @@
|
||||||
package gs.crypto.v0
|
|
||||||
|
|
||||||
import cats.effect.Sync
|
|
||||||
import java.security.KeyFactory
|
|
||||||
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.
|
|
||||||
*/
|
|
||||||
trait KeyLoader[F[_]]:
|
|
||||||
/** 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
|
|
||||||
* The location of the key.
|
|
||||||
* @return
|
|
||||||
* The key's raw bytes - ready for further decoding.
|
|
||||||
*/
|
|
||||||
def loadKey(location: String): F[Either[KeyLoadError, Array[Byte]]]
|
|
||||||
|
|
||||||
object KeyLoader:
|
|
||||||
|
|
||||||
def stringLoader[F[_]: Sync](
|
|
||||||
config: BaseKeyLoader.Config = BaseKeyLoader.Defaults.Config
|
|
||||||
): StringKeyLoader[F] =
|
|
||||||
new StringKeyLoader(config)
|
|
||||||
|
|
||||||
def fileLoader[F[_]: Sync](
|
|
||||||
config: BaseKeyLoader.Config = BaseKeyLoader.Defaults.Config
|
|
||||||
): FileKeyLoader[F] =
|
|
||||||
new FileKeyLoader(config)
|
|
||||||
|
|
||||||
def resourceLoader[F[_]: Sync](
|
|
||||||
config: BaseKeyLoader.Config = BaseKeyLoader.Defaults.Config
|
|
||||||
): ResourceKeyLoader[F] =
|
|
||||||
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
|
|
||||||
|
|
@ -0,0 +1,77 @@
|
||||||
|
package gs.crypto.v0
|
||||||
|
|
||||||
|
import cats.Applicative
|
||||||
|
import cats.effect.Sync
|
||||||
|
import java.security.SecureRandom
|
||||||
|
|
||||||
|
/** Utility which produces random bytes. Used for cryptographic support.
|
||||||
|
*/
|
||||||
|
trait RandomByteProvider[F[_]]:
|
||||||
|
/** Generate the specified number of random bytes.
|
||||||
|
*
|
||||||
|
* @param numberOfBytes
|
||||||
|
* The number of bytes to generate.
|
||||||
|
* @return
|
||||||
|
* New array with the specified number of random bytes.
|
||||||
|
*/
|
||||||
|
def generateBytes(numberOfBytes: Int): F[Array[Byte]]
|
||||||
|
|
||||||
|
object RandomByteProvider:
|
||||||
|
|
||||||
|
/** Default random number generator.
|
||||||
|
*/
|
||||||
|
lazy val RNG: SecureRandom = new SecureRandom
|
||||||
|
|
||||||
|
/** New [[RandomByteProvider]] that uses the default (secure) random number
|
||||||
|
* generator to produce bytes.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
* The new provider instance.
|
||||||
|
*/
|
||||||
|
def secureRandom[F[_]: Sync]: RandomByteProvider[F] =
|
||||||
|
new SecureRandomProvider[F](RNG)
|
||||||
|
|
||||||
|
/** New [[RandomByteProvider]] that uses the given `SecureRandom` instance to
|
||||||
|
* produce bytes.
|
||||||
|
*
|
||||||
|
* @param random
|
||||||
|
* The `SecureRandom` instance.
|
||||||
|
* @return
|
||||||
|
* The new provider.
|
||||||
|
*/
|
||||||
|
def secureRandom[F[_]: Sync](random: SecureRandom): RandomByteProvider[F] =
|
||||||
|
new SecureRandomProvider[F](random)
|
||||||
|
|
||||||
|
/** @return
|
||||||
|
* New [[RandomByteProvider]] that returns an array of the null byte.
|
||||||
|
*/
|
||||||
|
def zero[F[_]: Applicative]: RandomByteProvider[F] =
|
||||||
|
new Zero[F]
|
||||||
|
|
||||||
|
/** Implementation of [[RandomByteProvider]] that uses `SecureRandom`.
|
||||||
|
*
|
||||||
|
* @param random
|
||||||
|
* The random number generator.
|
||||||
|
*/
|
||||||
|
final class SecureRandomProvider[F[_]: Sync](
|
||||||
|
val random: SecureRandom
|
||||||
|
) extends RandomByteProvider[F]:
|
||||||
|
|
||||||
|
/** @inheritDocs
|
||||||
|
*/
|
||||||
|
override def generateBytes(numberOfBytes: Int): F[Array[Byte]] =
|
||||||
|
Sync[F].delay {
|
||||||
|
val b = new Array[Byte](numberOfBytes)
|
||||||
|
random.nextBytes(b)
|
||||||
|
b
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Implementation of [[RandomByteProvider]] that always returns an array of
|
||||||
|
* the null byte.
|
||||||
|
*/
|
||||||
|
final class Zero[F[_]: Applicative] extends RandomByteProvider[F]:
|
||||||
|
|
||||||
|
override def generateBytes(numberOfBytes: Int): F[Array[Byte]] =
|
||||||
|
Applicative[F].pure(Array.fill(numberOfBytes)(0))
|
||||||
|
|
||||||
|
end RandomByteProvider
|
||||||
|
|
@ -1,26 +0,0 @@
|
||||||
package gs.crypto.v0
|
|
||||||
|
|
||||||
import cats.effect.Resource
|
|
||||||
import cats.effect.Sync
|
|
||||||
import scala.io.Source
|
|
||||||
|
|
||||||
/** Implementation of [[KeyLoader]] that loads keys from encoded string files
|
|
||||||
* that exist as project resources.
|
|
||||||
*
|
|
||||||
* @param config
|
|
||||||
* The key loader configuration.
|
|
||||||
*/
|
|
||||||
class ResourceKeyLoader[F[_]: Sync](
|
|
||||||
val config: BaseKeyLoader.Config
|
|
||||||
) extends BaseKeyLoader[F]:
|
|
||||||
|
|
||||||
/** @inheritDocs
|
|
||||||
*/
|
|
||||||
override def loadKey(
|
|
||||||
location: String
|
|
||||||
): F[Either[KeyLoadError, Array[Byte]]] =
|
|
||||||
Resource
|
|
||||||
.make(Sync[F].delay(Source.fromResource(location)))(source =>
|
|
||||||
Sync[F].delay(source.close())
|
|
||||||
)
|
|
||||||
.use(source => Sync[F].delay(prepareKey(source.getLines().mkString)))
|
|
||||||
|
|
@ -1,58 +0,0 @@
|
||||||
package gs.crypto.v0
|
|
||||||
|
|
||||||
import gs.std.v0.core.B64
|
|
||||||
import gs.std.v0.core.B64Url
|
|
||||||
import gs.std.v0.core.Base64Encoder
|
|
||||||
import gs.std.v0.core.EncodedString
|
|
||||||
import gs.std.v0.core.Hex
|
|
||||||
import gs.std.v0.core.HexEncoder
|
|
||||||
|
|
||||||
/** Represents a cryptographic signature. Opaque type for an array of bytes.
|
|
||||||
*/
|
|
||||||
opaque type Signature = Array[Byte]
|
|
||||||
|
|
||||||
object Signature:
|
|
||||||
|
|
||||||
/** Instantiate a new signature from the given byte array.
|
|
||||||
*
|
|
||||||
* @param value
|
|
||||||
* The signature value.
|
|
||||||
* @return
|
|
||||||
* The signature bytes.
|
|
||||||
*/
|
|
||||||
def apply(value: Array[Byte]): Signature = value
|
|
||||||
|
|
||||||
/** Instantiate a new signature from the given encoded string.
|
|
||||||
*
|
|
||||||
* @param value
|
|
||||||
* The input data.
|
|
||||||
* @return
|
|
||||||
* The new signature.
|
|
||||||
*/
|
|
||||||
def fromEncodedString(value: EncodedString): Signature =
|
|
||||||
value.decode()
|
|
||||||
|
|
||||||
given CanEqual[Signature, Signature] = CanEqual.derived
|
|
||||||
|
|
||||||
extension (sig: Signature)
|
|
||||||
/** @return
|
|
||||||
* The underlying byte array.
|
|
||||||
*/
|
|
||||||
def unwrap(): Array[Byte] = sig
|
|
||||||
|
|
||||||
/** @return
|
|
||||||
* This signature, encoded using base64.
|
|
||||||
*/
|
|
||||||
def toBase64(): B64 = Base64Encoder.encode(sig)
|
|
||||||
|
|
||||||
/** @return
|
|
||||||
* This signature, encoded using base64-url.
|
|
||||||
*/
|
|
||||||
def toBase64Url(): B64Url = Base64Encoder.encodeUrl(sig)
|
|
||||||
|
|
||||||
/** @return
|
|
||||||
* This signature, encoded using hex.
|
|
||||||
*/
|
|
||||||
def toHex(): Hex = HexEncoder.encode(sig)
|
|
||||||
|
|
||||||
end Signature
|
|
||||||
|
|
@ -1,50 +0,0 @@
|
||||||
package gs.crypto.v0
|
|
||||||
|
|
||||||
/** Used to communicate the calculated validity of some [[Signature]].
|
|
||||||
*
|
|
||||||
* @param name
|
|
||||||
* The enumeration name.
|
|
||||||
*/
|
|
||||||
sealed abstract class SignatureValidity(val name: String):
|
|
||||||
|
|
||||||
/** @inheritDocs
|
|
||||||
*/
|
|
||||||
override def equals(obj: Any): Boolean =
|
|
||||||
obj match
|
|
||||||
case other: SignatureValidity => name == other.name
|
|
||||||
case _ => false
|
|
||||||
|
|
||||||
/** @inheritDocs
|
|
||||||
*/
|
|
||||||
override def hashCode(): Int = name.hashCode()
|
|
||||||
|
|
||||||
/** @inheritDocs
|
|
||||||
*/
|
|
||||||
override def toString(): String = name
|
|
||||||
|
|
||||||
object SignatureValidity:
|
|
||||||
|
|
||||||
/** Map a Boolean value to validity.
|
|
||||||
*
|
|
||||||
* - True: Valid
|
|
||||||
* - False: Invalid
|
|
||||||
*
|
|
||||||
* @param isValid
|
|
||||||
* Whether the signature is valid or not.
|
|
||||||
* @return
|
|
||||||
* The [[SignatureValidity]] selected for the given validity.
|
|
||||||
*/
|
|
||||||
def apply(isValid: Boolean): SignatureValidity =
|
|
||||||
if isValid then Valid else Invalid
|
|
||||||
|
|
||||||
given CanEqual[SignatureValidity, SignatureValidity] = CanEqual.derived
|
|
||||||
|
|
||||||
/** The [[Signature]] is valid.
|
|
||||||
*/
|
|
||||||
case object Valid extends SignatureValidity("valid")
|
|
||||||
|
|
||||||
/** The [[Signature]] is invalid.
|
|
||||||
*/
|
|
||||||
case object Invalid extends SignatureValidity("invalid")
|
|
||||||
|
|
||||||
end SignatureValidity
|
|
||||||
|
|
@ -1,10 +0,0 @@
|
||||||
package gs.crypto.v0
|
|
||||||
|
|
||||||
/** Used to verify cryptographic signatures.
|
|
||||||
*/
|
|
||||||
trait SignatureVerifier[F[_]]:
|
|
||||||
|
|
||||||
def verify(
|
|
||||||
signature: Signature,
|
|
||||||
data: Array[Byte]
|
|
||||||
): F[SignatureValidity]
|
|
||||||
|
|
@ -1,30 +0,0 @@
|
||||||
package gs.crypto.v0
|
|
||||||
|
|
||||||
import java.nio.charset.Charset
|
|
||||||
import java.nio.charset.StandardCharsets
|
|
||||||
|
|
||||||
/** Used to calculate cryptographic signatures.
|
|
||||||
*/
|
|
||||||
trait Signer[F[_]]:
|
|
||||||
/** Calculate a signature for the given data.
|
|
||||||
*
|
|
||||||
* @param data
|
|
||||||
* The input data.
|
|
||||||
* @return
|
|
||||||
* The calculated signature.
|
|
||||||
*/
|
|
||||||
def sign(data: Array[Byte]): F[Signature]
|
|
||||||
|
|
||||||
/** Calculate a signature for the given data.
|
|
||||||
*
|
|
||||||
* @param data
|
|
||||||
* The input data.
|
|
||||||
* @param charset
|
|
||||||
* The character set of the input data.
|
|
||||||
* @return
|
|
||||||
* The calculated signature.
|
|
||||||
*/
|
|
||||||
def sign(
|
|
||||||
data: String,
|
|
||||||
charset: Charset = StandardCharsets.UTF_8
|
|
||||||
): F[Signature]
|
|
||||||
|
|
@ -1,15 +0,0 @@
|
||||||
package gs.crypto.v0
|
|
||||||
|
|
||||||
import cats.effect.Sync
|
|
||||||
|
|
||||||
/** Implementation of [[KeyLoader]] that loads keys directly from input strings.
|
|
||||||
*
|
|
||||||
* @param config
|
|
||||||
* The key loader configuration.
|
|
||||||
*/
|
|
||||||
class StringKeyLoader[F[_]: Sync](
|
|
||||||
val config: BaseKeyLoader.Config
|
|
||||||
) extends BaseKeyLoader[F]:
|
|
||||||
|
|
||||||
override def loadKey(location: String): F[Either[KeyLoadError, Array[Byte]]] =
|
|
||||||
Sync[F].delay(prepareKey(location))
|
|
||||||
|
|
@ -1,7 +0,0 @@
|
||||||
package gs.crypto.v0.eddsa
|
|
||||||
|
|
||||||
object Ed25519:
|
|
||||||
|
|
||||||
val Algorithm: String = "Ed25519"
|
|
||||||
|
|
||||||
end Ed25519
|
|
||||||
|
|
@ -1,38 +0,0 @@
|
||||||
package gs.crypto.v0.eddsa
|
|
||||||
|
|
||||||
import cats.effect.Sync
|
|
||||||
import gs.crypto.v0.Signature
|
|
||||||
import gs.crypto.v0.Signer
|
|
||||||
import java.nio.charset.Charset
|
|
||||||
import java.nio.charset.StandardCharsets
|
|
||||||
import java.security.PrivateKey
|
|
||||||
|
|
||||||
/** Implementation of [[Signer]] that uses the Ed25519 system to calculate
|
|
||||||
* signatures.
|
|
||||||
*
|
|
||||||
* See: [[Ed25519Verifier]]
|
|
||||||
*
|
|
||||||
* @param privateKey
|
|
||||||
* The private key.
|
|
||||||
*/
|
|
||||||
final class Ed25519Signer[F[_]: Sync](
|
|
||||||
privateKey: PrivateKey
|
|
||||||
) extends Signer[F]:
|
|
||||||
|
|
||||||
/** @inheritDocs
|
|
||||||
*/
|
|
||||||
override def sign(data: Array[Byte]): F[Signature] =
|
|
||||||
Sync[F].delay {
|
|
||||||
val s = java.security.Signature.getInstance(Ed25519.Algorithm)
|
|
||||||
val _ = s.initSign(privateKey)
|
|
||||||
val _ = s.update(data)
|
|
||||||
Signature(s.sign())
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @inheritDocs
|
|
||||||
*/
|
|
||||||
override def sign(
|
|
||||||
data: String,
|
|
||||||
charset: Charset = StandardCharsets.UTF_8
|
|
||||||
): F[Signature] =
|
|
||||||
sign(data.getBytes(charset))
|
|
||||||
|
|
@ -1,32 +0,0 @@
|
||||||
package gs.crypto.v0.eddsa
|
|
||||||
|
|
||||||
import cats.effect.Sync
|
|
||||||
import gs.crypto.v0.Signature
|
|
||||||
import gs.crypto.v0.SignatureValidity
|
|
||||||
import gs.crypto.v0.SignatureVerifier
|
|
||||||
import java.security.PublicKey
|
|
||||||
|
|
||||||
/** Implementation of [[SignatureVerifier]] that uses the Ed25519 system to
|
|
||||||
* verify signatures.
|
|
||||||
*
|
|
||||||
* See: [[Ed25519Signer]]
|
|
||||||
*
|
|
||||||
* @param publicKey
|
|
||||||
* The public key.
|
|
||||||
*/
|
|
||||||
final class Ed25519Verifier[F[_]: Sync](
|
|
||||||
publicKey: PublicKey
|
|
||||||
) extends SignatureVerifier[F]:
|
|
||||||
|
|
||||||
/** @inheritDocs
|
|
||||||
*/
|
|
||||||
override def verify(
|
|
||||||
signature: Signature,
|
|
||||||
data: Array[Byte]
|
|
||||||
): F[SignatureValidity] =
|
|
||||||
Sync[F].delay {
|
|
||||||
val s = java.security.Signature.getInstance(Ed25519.Algorithm)
|
|
||||||
val _ = s.initVerify(publicKey)
|
|
||||||
val _ = s.update(data)
|
|
||||||
SignatureValidity(s.verify(signature.unwrap()))
|
|
||||||
}
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
-----BEGIN PRIVATE KEY-----
|
|
||||||
MC4CAQAwBQYDK2VwBCIEIPkucDMoZPkyltc200nNyyfJKWP7cu3Tr9gdsFB6PeqN
|
|
||||||
-----END PRIVATE KEY-----
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
-----BEGIN PUBLIC KEY-----
|
|
||||||
MCowBQYDK2VwAyEAYNI09OpzbZ9GiKhZzNEbwWYISkR2ihfvWEsZx62b+bY=
|
|
||||||
-----END PUBLIC KEY-----
|
|
||||||
|
|
@ -1,61 +0,0 @@
|
||||||
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
|
|
||||||
|
|
@ -3,9 +3,9 @@ package gs.crypto.v0.rsa
|
||||||
import cats.effect.Sync
|
import cats.effect.Sync
|
||||||
import cats.effect.kernel.Resource
|
import cats.effect.kernel.Resource
|
||||||
import cats.syntax.all.*
|
import cats.syntax.all.*
|
||||||
import gs.std.v0.core.Base64Decoder
|
import gs.crypto.v0.Base64Decoder
|
||||||
import gs.std.v0.io.Files
|
|
||||||
import java.nio.charset.StandardCharsets
|
import java.nio.charset.StandardCharsets
|
||||||
|
import java.nio.file.Files
|
||||||
import java.nio.file.Path
|
import java.nio.file.Path
|
||||||
import java.security.KeyFactory
|
import java.security.KeyFactory
|
||||||
import java.security.PrivateKey
|
import java.security.PrivateKey
|
||||||
|
|
@ -116,8 +116,8 @@ object Rsa:
|
||||||
def loadPublicKeyFromFile[F[_]: Sync](
|
def loadPublicKeyFromFile[F[_]: Sync](
|
||||||
publicKeyPath: Path
|
publicKeyPath: Path
|
||||||
): F[PublicKey] =
|
): F[PublicKey] =
|
||||||
Files
|
Sync[F]
|
||||||
.readFileAsString(publicKeyPath, StandardCharsets.UTF_8)
|
.delay(Files.readString(publicKeyPath, StandardCharsets.UTF_8))
|
||||||
.map(preparePublicKey)
|
.map(preparePublicKey)
|
||||||
.flatMap(loadPublicKey[F])
|
.flatMap(loadPublicKey[F])
|
||||||
|
|
||||||
|
|
@ -144,8 +144,8 @@ object Rsa:
|
||||||
def loadPrivateKeyFromFile[F[_]: Sync](
|
def loadPrivateKeyFromFile[F[_]: Sync](
|
||||||
privateKeyPath: Path
|
privateKeyPath: Path
|
||||||
): F[PrivateKey] =
|
): F[PrivateKey] =
|
||||||
Files
|
Sync[F]
|
||||||
.readFileAsString(privateKeyPath, StandardCharsets.UTF_8)
|
.delay(Files.readString(privateKeyPath, StandardCharsets.UTF_8))
|
||||||
.map(preparePrivateKey)
|
.map(preparePrivateKey)
|
||||||
.flatMap(loadPrivateKey[F])
|
.flatMap(loadPrivateKey[F])
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
package gs.crypto.v0.rsa
|
package gs.crypto.v0.rsa
|
||||||
|
|
||||||
import gs.std.v0.core.EncodedString
|
import gs.crypto.v0.Encoded
|
||||||
import gs.std.v0.core.Encoder
|
import gs.crypto.v0.Encoder
|
||||||
|
|
||||||
/** Represents arbitrary bytes that were encrypted using RSA.
|
/** Represents arbitrary bytes that were encrypted using RSA.
|
||||||
*
|
*
|
||||||
|
|
@ -15,8 +15,7 @@ import gs.std.v0.core.Encoder
|
||||||
*/
|
*/
|
||||||
final class RsaEncryptedBytes(val bytes: Array[Byte]):
|
final class RsaEncryptedBytes(val bytes: Array[Byte]):
|
||||||
|
|
||||||
def encode(encoder: Encoder[EncodedString] = Encoder.base64())
|
def encode(encoder: Encoder[Encoded] = Encoder.base64()): Encoded =
|
||||||
: EncodedString =
|
|
||||||
encoder.encode(bytes)
|
encoder.encode(bytes)
|
||||||
|
|
||||||
/** @inheritDocs
|
/** @inheritDocs
|
||||||
|
|
@ -40,7 +39,7 @@ object RsaEncryptedBytes:
|
||||||
* @return
|
* @return
|
||||||
* The new [[RsaEncryptedBytes]].
|
* The new [[RsaEncryptedBytes]].
|
||||||
*/
|
*/
|
||||||
def decode(value: EncodedString): RsaEncryptedBytes =
|
def decode(value: Encoded): RsaEncryptedBytes =
|
||||||
new RsaEncryptedBytes(value.decode())
|
new RsaEncryptedBytes(value.decode())
|
||||||
|
|
||||||
end RsaEncryptedBytes
|
end RsaEncryptedBytes
|
||||||
|
|
|
||||||
|
|
@ -1 +1 @@
|
||||||
sbt.version=1.12.4
|
sbt.version=1.12.2
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue