Compare commits
No commits in common. "f4bbd53d44f1ed2bce46a1466004a0dca3683ddc" and "9bef6a92ffd50c543adc693d8805eb6c15514a14" have entirely different histories.
f4bbd53d44
...
9bef6a92ff
18 changed files with 3 additions and 546 deletions
17
build.sbt
17
build.sbt
|
|
@ -52,7 +52,7 @@ lazy val testSettings = Seq(
|
|||
|
||||
lazy val `gs-crypto` = project
|
||||
.in(file("."))
|
||||
.aggregate(core, argon2, rsa, eddsa)
|
||||
.aggregate(core, argon2, rsa)
|
||||
.settings(noPublishSettings)
|
||||
.settings(name := s"${gsProjectName.value}-v${semVerMajor.value}")
|
||||
|
||||
|
|
@ -97,18 +97,3 @@ lazy val rsa = project
|
|||
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
|
||||
)
|
||||
)
|
||||
|
|
|
|||
|
|
@ -3,12 +3,9 @@ package gs.crypto.v0
|
|||
import java.{util => ju}
|
||||
|
||||
/** Implementation of [[Decoder]] for Base64 strings.
|
||||
*
|
||||
* Supports base64-url decoding as well.
|
||||
*/
|
||||
object Base64Decoder extends Decoder[B64]:
|
||||
private lazy val d: ju.Base64.Decoder = ju.Base64.getDecoder()
|
||||
private lazy val du: ju.Base64.Decoder = ju.Base64.getUrlDecoder()
|
||||
|
||||
/** @inheritDocs
|
||||
*/
|
||||
|
|
@ -24,13 +21,3 @@ object Base64Decoder extends Decoder[B64]:
|
|||
*/
|
||||
def decodeUnsafe(input: String): Array[Byte] =
|
||||
d.decode(input)
|
||||
|
||||
/** Decode the base64-url encoded input.
|
||||
*
|
||||
* @param input
|
||||
* The base64-url encoded data.
|
||||
* @return
|
||||
* The decoded bytes.
|
||||
*/
|
||||
def decodeUrl(input: B64Url): Array[Byte] =
|
||||
du.decode(input.data)
|
||||
|
|
|
|||
|
|
@ -5,12 +5,9 @@ import java.nio.charset.StandardCharsets
|
|||
import java.util.Base64
|
||||
|
||||
/** Implementation of [[Encoder]] for Base64.
|
||||
*
|
||||
* Supports base64-url encoding as well.
|
||||
*/
|
||||
object Base64Encoder extends Encoder[B64]:
|
||||
private lazy val e: Base64.Encoder = Base64.getEncoder()
|
||||
private lazy val eu: Base64.Encoder = Base64.getUrlEncoder()
|
||||
|
||||
/** @inheritDocs
|
||||
*/
|
||||
|
|
@ -24,28 +21,3 @@ object Base64Encoder extends Encoder[B64]:
|
|||
charset: Charset = StandardCharsets.UTF_8
|
||||
): B64 =
|
||||
encode(input.getBytes(charset))
|
||||
|
||||
/** Encode the given bytes using base64-url.
|
||||
*
|
||||
* @param input
|
||||
* The input data.
|
||||
* @return
|
||||
* The base64-url-encoded string.
|
||||
*/
|
||||
def encodeUrl(input: Array[Byte]): B64Url =
|
||||
B64Url(eu.encodeToString(input))
|
||||
|
||||
/** Encode the given bytes using base64-url.
|
||||
*
|
||||
* @param input
|
||||
* The input data.
|
||||
* @param charset
|
||||
* The character set of the input data.
|
||||
* @return
|
||||
* The base64-url-encoded string.
|
||||
*/
|
||||
def encodeUrl(
|
||||
input: String,
|
||||
charset: Charset = StandardCharsets.UTF_8
|
||||
): B64Url =
|
||||
encodeUrl(input.getBytes(charset))
|
||||
|
|
|
|||
|
|
@ -1,95 +0,0 @@
|
|||
package gs.crypto.v0
|
||||
|
||||
/** Base implementation for [[KeyLoader]] that provides standard parsing
|
||||
* functionality.
|
||||
*/
|
||||
abstract class BaseKeyLoader[F[_]] extends KeyLoader[F]:
|
||||
/** @return
|
||||
* The key loader configuration.
|
||||
*/
|
||||
def config: BaseKeyLoader.Config
|
||||
|
||||
protected def prepareKey(base: String): Either[KeyLoadError, Array[Byte]] =
|
||||
scala.util
|
||||
.Try {
|
||||
config.decoder.decode(trim(collapse(unwrap(base))))
|
||||
}
|
||||
.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[String]
|
||||
)
|
||||
|
||||
/** 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.decodeUnsafe(s)
|
||||
)
|
||||
|
||||
end Defaults
|
||||
|
||||
end BaseKeyLoader
|
||||
|
|
@ -23,11 +23,6 @@ trait Encoded:
|
|||
* The encoded data.
|
||||
*/
|
||||
|
||||
/** Represents Base64-encoded data.
|
||||
*
|
||||
* @param data
|
||||
* The encoded data.
|
||||
*/
|
||||
final class B64(
|
||||
val data: String
|
||||
) extends Encoded:
|
||||
|
|
@ -68,51 +63,6 @@ object B64:
|
|||
|
||||
end B64
|
||||
|
||||
/** Represents Base64-url-encoded data.
|
||||
*
|
||||
* @param data
|
||||
* The encoded data.
|
||||
*/
|
||||
final class B64Url(
|
||||
val data: String
|
||||
) extends Encoded:
|
||||
/** @inheritDocs
|
||||
*/
|
||||
def decode(): Array[Byte] = Base64Decoder.decodeUrl(this)
|
||||
|
||||
/** @inheritDocs
|
||||
*/
|
||||
override def equals(obj: Any): Boolean =
|
||||
obj match
|
||||
case other: B64Url => data == other.data
|
||||
|
||||
/** @inheritDocs
|
||||
*/
|
||||
override def hashCode(): Int = data.hashCode()
|
||||
|
||||
/** @inheritDocs
|
||||
*/
|
||||
override def toString(): String = data
|
||||
|
||||
object B64Url:
|
||||
|
||||
/** Instantiate [[B64Url]] from the given string.
|
||||
*
|
||||
* This function does NOT validate the input.
|
||||
*
|
||||
* @param data
|
||||
* The encoded data.
|
||||
* @return
|
||||
* The new [[B64Url]] instance.
|
||||
*/
|
||||
def apply(
|
||||
data: String
|
||||
): B64Url = new B64Url(data)
|
||||
|
||||
given CanEqual[B64Url, B64Url] = CanEqual.derived
|
||||
|
||||
end B64Url
|
||||
|
||||
/** Represents Hex-encoded data.
|
||||
*
|
||||
* @param data
|
||||
|
|
|
|||
|
|
@ -1,32 +0,0 @@
|
|||
package gs.crypto.v0
|
||||
|
||||
import cats.effect.Sync
|
||||
import cats.syntax.all.*
|
||||
import java.nio.charset.Charset
|
||||
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,
|
||||
val charset: Charset
|
||||
) 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, charset)).map(prepareKey)
|
||||
}
|
||||
|
|
@ -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,38 +0,0 @@
|
|||
package gs.crypto.v0
|
||||
|
||||
import cats.Applicative
|
||||
import cats.effect.Sync
|
||||
import java.nio.charset.Charset
|
||||
import java.nio.charset.StandardCharsets
|
||||
|
||||
/** Interface for loading keys (e.g. public/private keys) into memory.
|
||||
*/
|
||||
trait KeyLoader[F[_]]:
|
||||
/** Load the key from the given location.
|
||||
*
|
||||
* @param location
|
||||
* The location of the key.
|
||||
* @return
|
||||
* The loaded key.
|
||||
*/
|
||||
def loadKey(location: String): F[Either[KeyLoadError, Array[Byte]]]
|
||||
|
||||
object KeyLoader:
|
||||
|
||||
def stringLoader[F[_]: Applicative](
|
||||
config: BaseKeyLoader.Config
|
||||
): StringKeyLoader[F] =
|
||||
new StringKeyLoader[F](config)
|
||||
|
||||
def fileLoader[F[_]: Sync](
|
||||
config: BaseKeyLoader.Config,
|
||||
charset: Charset = StandardCharsets.UTF_8
|
||||
): FileKeyLoader[F] =
|
||||
new FileKeyLoader(config, charset)
|
||||
|
||||
def resourceLoader[F[_]: Sync](
|
||||
config: BaseKeyLoader.Config
|
||||
): ResourceKeyLoader[F] =
|
||||
new ResourceKeyLoader(config)
|
||||
|
||||
end KeyLoader
|
||||
|
|
@ -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,51 +0,0 @@
|
|||
package gs.crypto.v0
|
||||
|
||||
/** 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 fromEncoded(value: Encoded): 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,6 +0,0 @@
|
|||
package gs.crypto.v0
|
||||
|
||||
/** Used to verify cryptographic signatures.
|
||||
*/
|
||||
trait SignatureVerifier[F[_]]:
|
||||
def verify(signature: Signature): 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,19 +0,0 @@
|
|||
package gs.crypto.v0
|
||||
|
||||
import cats.Applicative
|
||||
|
||||
/** Implementation of [[KeyLoader]] that loads keys directly from input strings.
|
||||
*
|
||||
* @param config
|
||||
* The key loader configuration.
|
||||
*/
|
||||
class StringKeyLoader[F[_]: Applicative](
|
||||
val config: BaseKeyLoader.Config
|
||||
) extends BaseKeyLoader[F]:
|
||||
|
||||
/** @inheritDocs
|
||||
*/
|
||||
override def loadKey(
|
||||
location: String
|
||||
): F[Either[KeyLoadError, Array[Byte]]] =
|
||||
Applicative[F].pure(prepareKey(location))
|
||||
|
|
@ -1,39 +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 org.bouncycastle.crypto.params.Ed25519PrivateKeyParameters
|
||||
|
||||
/** Implementation of [[Signer]] that uses the Ed25519 system to calculate
|
||||
* signatures.
|
||||
*
|
||||
* See: [[Ed25519Verifier]]
|
||||
*
|
||||
* @param privateKey
|
||||
* The private key.
|
||||
*/
|
||||
final class Ed25519Signer[F[_]: Sync](
|
||||
privateKey: Array[Byte]
|
||||
) extends Signer[F]:
|
||||
private lazy val params = new Ed25519PrivateKeyParameters(privateKey)
|
||||
|
||||
/** @inheritDocs
|
||||
*/
|
||||
override def sign(data: Array[Byte]): F[Signature] =
|
||||
Sync[F].delay {
|
||||
val s = new org.bouncycastle.crypto.signers.Ed25519Signer()
|
||||
val _ = s.init(true, params)
|
||||
val _ = s.update(data, 0, data.length)
|
||||
Signature(s.generateSignature())
|
||||
}
|
||||
|
||||
/** @inheritDocs
|
||||
*/
|
||||
override def sign(
|
||||
data: String,
|
||||
charset: Charset = StandardCharsets.UTF_8
|
||||
): F[Signature] =
|
||||
sign(data.getBytes(charset))
|
||||
|
|
@ -1,29 +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 org.bouncycastle.crypto.params.Ed25519PublicKeyParameters
|
||||
|
||||
/** Implementation of [[SignatureVerifier]] that uses the Ed25519 system to
|
||||
* verify signatures.
|
||||
*
|
||||
* See: [[Ed25519Signer]]
|
||||
*
|
||||
* @param publicKey
|
||||
* The public key.
|
||||
*/
|
||||
final class Ed25519Verifier[F[_]: Sync](
|
||||
publicKey: Array[Byte]
|
||||
) extends SignatureVerifier[F]:
|
||||
private lazy val params = new Ed25519PublicKeyParameters(publicKey)
|
||||
|
||||
/** @inheritDocs
|
||||
*/
|
||||
override def verify(signature: Signature): F[SignatureValidity] =
|
||||
Sync[F].delay {
|
||||
val s = new org.bouncycastle.crypto.signers.Ed25519Signer()
|
||||
val _ = s.init(false, params)
|
||||
SignatureValidity(s.verifySignature(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-----
|
||||
Loading…
Add table
Reference in a new issue