rsa works
This commit is contained in:
parent
a8ba253ea1
commit
a7e2185204
8 changed files with 504 additions and 0 deletions
48
modules/auth/src/main/scala/gs/smolban/auth/Base64.scala
Normal file
48
modules/auth/src/main/scala/gs/smolban/auth/Base64.scala
Normal file
|
|
@ -0,0 +1,48 @@
|
||||||
|
package gs.smolban.auth
|
||||||
|
|
||||||
|
import cats.Eq
|
||||||
|
import cats.Show
|
||||||
|
|
||||||
|
/** Opaque type for a Base64-encoded String.
|
||||||
|
*/
|
||||||
|
opaque type Base64 = String
|
||||||
|
|
||||||
|
object Base64:
|
||||||
|
|
||||||
|
given CanEqual[Base64, Base64] = CanEqual.derived
|
||||||
|
|
||||||
|
given Show[Base64] = b64 => b64
|
||||||
|
|
||||||
|
given Eq[Base64] = (
|
||||||
|
x,
|
||||||
|
y
|
||||||
|
) => x == y
|
||||||
|
|
||||||
|
private lazy val encoder = java.util.Base64.getEncoder()
|
||||||
|
private lazy val decoder = java.util.Base64.getDecoder()
|
||||||
|
|
||||||
|
/** Instantiate a new [[Base64]] instance by encoding the given bytes.
|
||||||
|
*
|
||||||
|
* @param input
|
||||||
|
* The input bytes.
|
||||||
|
* @return
|
||||||
|
* The encoded string representation of the given bytes.
|
||||||
|
*/
|
||||||
|
def encode(input: Array[Byte]): Base64 =
|
||||||
|
encoder.encodeToString(input)
|
||||||
|
|
||||||
|
def decodeUnsafe(input: String): Array[Byte] =
|
||||||
|
decoder.decode(input)
|
||||||
|
|
||||||
|
extension (b64: Base64)
|
||||||
|
/** @return
|
||||||
|
* The decoded byte array that this string represents.
|
||||||
|
*/
|
||||||
|
def decode(): Array[Byte] = decoder.decode(b64)
|
||||||
|
|
||||||
|
/** @return
|
||||||
|
* The underlying string value.
|
||||||
|
*/
|
||||||
|
def unwrap(): String = b64
|
||||||
|
|
||||||
|
end Base64
|
||||||
180
modules/auth/src/main/scala/gs/smolban/auth/Rsa.scala
Normal file
180
modules/auth/src/main/scala/gs/smolban/auth/Rsa.scala
Normal file
|
|
@ -0,0 +1,180 @@
|
||||||
|
package gs.smolban.auth
|
||||||
|
|
||||||
|
import cats.effect.Sync
|
||||||
|
import cats.effect.kernel.Resource
|
||||||
|
import cats.syntax.all.*
|
||||||
|
import java.nio.charset.StandardCharsets
|
||||||
|
import java.nio.file.Files
|
||||||
|
import java.nio.file.Path
|
||||||
|
import java.security.KeyFactory
|
||||||
|
import java.security.PrivateKey
|
||||||
|
import java.security.PublicKey
|
||||||
|
import java.security.spec.PKCS8EncodedKeySpec
|
||||||
|
import java.security.spec.X509EncodedKeySpec
|
||||||
|
import scala.io.Source
|
||||||
|
|
||||||
|
/** Support for the RSA asymmetric encryption algorithm.
|
||||||
|
*
|
||||||
|
* See:
|
||||||
|
*
|
||||||
|
* - [[RsaEncryption]]
|
||||||
|
* - [[RsaDecryption]]
|
||||||
|
* - [[RsaEncryptedBytes]]
|
||||||
|
*
|
||||||
|
* Use the following functions to get encryption and decryption tools:
|
||||||
|
*
|
||||||
|
* - `initializeEncryption`
|
||||||
|
* - `initializeEncryptionFromFile`
|
||||||
|
* - `initializeDecryption`
|
||||||
|
* - `initializeDecryptionFromFile`
|
||||||
|
*/
|
||||||
|
object Rsa:
|
||||||
|
|
||||||
|
val CipherName: String = "RSA/ECB/PKCS1Padding"
|
||||||
|
val Algorithm: String = "RSA"
|
||||||
|
|
||||||
|
/** Initialize a new instance of [[RsaEncryption]] for the given public key.
|
||||||
|
*
|
||||||
|
* This function assumes that the provided bytes do not need any further
|
||||||
|
* processing - they are already decoded from any encoded form.
|
||||||
|
*
|
||||||
|
* This function accepts X509 public keys.
|
||||||
|
*
|
||||||
|
* @param publicKeyRawBytes
|
||||||
|
* The raw bytes that constitute the public key.
|
||||||
|
* @return
|
||||||
|
* The new instance of [[RsaEncryption]].
|
||||||
|
*/
|
||||||
|
def initializeEncryption[F[_]: Sync](
|
||||||
|
publicKeyRawBytes: Array[Byte]
|
||||||
|
): F[RsaEncryption[F]] =
|
||||||
|
loadPublicKey(publicKeyRawBytes).map(new RsaEncryption(_))
|
||||||
|
|
||||||
|
/** Initialize a new instance of [[RsaEncryption]] for the given public key by
|
||||||
|
* loading that key from disk.
|
||||||
|
*
|
||||||
|
* @param publicKeyPath
|
||||||
|
* The path to the public key on local disk.
|
||||||
|
* @return
|
||||||
|
* The new instance of [[RsaDecryption]].
|
||||||
|
*/
|
||||||
|
def initializeEncryptionFromFile[F[_]: Sync](
|
||||||
|
publicKeyPath: Path
|
||||||
|
): F[RsaEncryption[F]] =
|
||||||
|
loadPublicKeyFromFile(publicKeyPath).map(new RsaEncryption(_))
|
||||||
|
|
||||||
|
def initializeEncryptionFromResource[F[_]: Sync](
|
||||||
|
resourceName: String
|
||||||
|
): F[RsaEncryption[F]] =
|
||||||
|
loadPublicKeyFromResource(resourceName).map(new RsaEncryption(_))
|
||||||
|
|
||||||
|
/** Initialize a new instance of [[RsaDecryption]] for the given private key.
|
||||||
|
*
|
||||||
|
* This function assumes that the provided bytes do not need any further
|
||||||
|
* processing - they are already decoded from any encoded form.
|
||||||
|
*
|
||||||
|
* This function accepts PKCS8 private keys.
|
||||||
|
*
|
||||||
|
* @param privateKeyRawBytes
|
||||||
|
* The raw bytes that constitute the private key.
|
||||||
|
* @return
|
||||||
|
* The new instance of [[RsaDecryption]].
|
||||||
|
*/
|
||||||
|
def initializeDecryption[F[_]: Sync](
|
||||||
|
privateKeyRawBytes: Array[Byte]
|
||||||
|
): F[RsaDecryption[F]] =
|
||||||
|
loadPrivateKey(privateKeyRawBytes).map(new RsaDecryption(_))
|
||||||
|
|
||||||
|
/** Initialize a new instance of [[RsaDecryption]] for the given private key
|
||||||
|
* by loading that key from disk.
|
||||||
|
*
|
||||||
|
* @param privateKeyPath
|
||||||
|
* The path to the private key on local disk.
|
||||||
|
* @return
|
||||||
|
* The new instance of [[RsaDecryption]].
|
||||||
|
*/
|
||||||
|
def initializeDecryptionFromFile[F[_]: Sync](
|
||||||
|
privateKeyPath: Path
|
||||||
|
): F[RsaDecryption[F]] =
|
||||||
|
loadPrivateKeyFromFile(privateKeyPath).map(new RsaDecryption(_))
|
||||||
|
|
||||||
|
def initializeDecryptionFromResource[F[_]: Sync](
|
||||||
|
privateKeyResourceName: String
|
||||||
|
): F[RsaDecryption[F]] =
|
||||||
|
loadPrivateKeyFromResource(privateKeyResourceName).map(new RsaDecryption(_))
|
||||||
|
|
||||||
|
def loadPublicKey[F[_]: Sync](
|
||||||
|
publicKeyRawBytes: Array[Byte]
|
||||||
|
): F[PublicKey] =
|
||||||
|
Sync[F].delay {
|
||||||
|
val spec = new X509EncodedKeySpec(publicKeyRawBytes)
|
||||||
|
val keyFactory = KeyFactory.getInstance(Rsa.Algorithm)
|
||||||
|
keyFactory.generatePublic(spec)
|
||||||
|
}
|
||||||
|
|
||||||
|
def loadPublicKeyFromFile[F[_]: Sync](
|
||||||
|
publicKeyPath: Path
|
||||||
|
): F[PublicKey] =
|
||||||
|
Sync[F]
|
||||||
|
.delay(Files.readString(publicKeyPath, StandardCharsets.UTF_8))
|
||||||
|
.map(preparePublicKey)
|
||||||
|
.flatMap(loadPublicKey[F])
|
||||||
|
|
||||||
|
def loadPublicKeyFromResource[F[_]: Sync](
|
||||||
|
resourceName: String
|
||||||
|
): F[PublicKey] =
|
||||||
|
Resource
|
||||||
|
.make(Sync[F].delay(Source.fromResource(resourceName)))(source =>
|
||||||
|
Sync[F].delay(source.close())
|
||||||
|
)
|
||||||
|
.use { source =>
|
||||||
|
loadPublicKey(preparePublicKey(source.getLines().mkString))
|
||||||
|
}
|
||||||
|
|
||||||
|
def loadPrivateKey[F[_]: Sync](
|
||||||
|
privateKeyRawBytes: Array[Byte]
|
||||||
|
): F[PrivateKey] =
|
||||||
|
Sync[F].delay {
|
||||||
|
val spec = new PKCS8EncodedKeySpec(privateKeyRawBytes)
|
||||||
|
val keyFactory = KeyFactory.getInstance(Rsa.Algorithm)
|
||||||
|
keyFactory.generatePrivate(spec)
|
||||||
|
}
|
||||||
|
|
||||||
|
def loadPrivateKeyFromFile[F[_]: Sync](
|
||||||
|
privateKeyPath: Path
|
||||||
|
): F[PrivateKey] =
|
||||||
|
Sync[F]
|
||||||
|
.delay(Files.readString(privateKeyPath, StandardCharsets.UTF_8))
|
||||||
|
.map(preparePrivateKey)
|
||||||
|
.flatMap(loadPrivateKey[F])
|
||||||
|
|
||||||
|
def loadPrivateKeyFromResource[F[_]: Sync](
|
||||||
|
resourceName: String
|
||||||
|
): F[PrivateKey] =
|
||||||
|
Resource
|
||||||
|
.make(Sync[F].delay(Source.fromResource(resourceName)))(source =>
|
||||||
|
Sync[F].delay(source.close())
|
||||||
|
)
|
||||||
|
.use { source =>
|
||||||
|
loadPrivateKey(preparePrivateKey(source.getLines().mkString))
|
||||||
|
}
|
||||||
|
|
||||||
|
private def preparePublicKey(base: String): Array[Byte] =
|
||||||
|
Base64.decodeUnsafe(
|
||||||
|
base
|
||||||
|
.replace("-----BEGIN PUBLIC KEY-----", "")
|
||||||
|
.replace("-----END PUBLIC KEY-----", "")
|
||||||
|
.replace("\n", "")
|
||||||
|
.trim()
|
||||||
|
)
|
||||||
|
|
||||||
|
private def preparePrivateKey(base: String): Array[Byte] =
|
||||||
|
Base64.decodeUnsafe(
|
||||||
|
base
|
||||||
|
.replace("-----BEGIN PRIVATE KEY-----", "")
|
||||||
|
.replace("-----END PRIVATE KEY-----", "")
|
||||||
|
.replace("\n", "")
|
||||||
|
.trim()
|
||||||
|
)
|
||||||
|
|
||||||
|
end Rsa
|
||||||
|
|
@ -0,0 +1,58 @@
|
||||||
|
package gs.smolban.auth
|
||||||
|
|
||||||
|
import cats.effect.Sync
|
||||||
|
import cats.syntax.all.*
|
||||||
|
import java.nio.charset.Charset
|
||||||
|
import java.nio.charset.StandardCharsets
|
||||||
|
import java.security.PrivateKey
|
||||||
|
import javax.crypto.Cipher
|
||||||
|
|
||||||
|
/** Utility for private key decryption using RSA.
|
||||||
|
*
|
||||||
|
* Intended to be used in conjunction with [[RsaEncryption]].
|
||||||
|
*
|
||||||
|
* @param privateKey
|
||||||
|
* The private key used to decrypt data.
|
||||||
|
*/
|
||||||
|
final class RsaDecryption[F[_]: Sync](privateKey: PrivateKey):
|
||||||
|
|
||||||
|
/** Decrypt the given bytes. These bytes have no guarantees regarding whether
|
||||||
|
* they were produced using RSA.
|
||||||
|
*
|
||||||
|
* @param input
|
||||||
|
* The input bytes to decrypt.
|
||||||
|
* @return
|
||||||
|
* The decrypted bytes. Throws an exception if decryption fails.
|
||||||
|
*/
|
||||||
|
def decryptUnsafe(input: Array[Byte]): F[Array[Byte]] =
|
||||||
|
Sync[F].delay {
|
||||||
|
val cipher: Cipher = Cipher.getInstance(Rsa.CipherName)
|
||||||
|
val _ = cipher.init(Cipher.DECRYPT_MODE, privateKey)
|
||||||
|
cipher.doFinal(input)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Decrypt the given bytes.
|
||||||
|
*
|
||||||
|
* @param input
|
||||||
|
* The encrypted bytes.
|
||||||
|
* @return
|
||||||
|
* The decrypted bytes.
|
||||||
|
*/
|
||||||
|
def decrypt(input: RsaEncryptedBytes): F[Array[Byte]] =
|
||||||
|
decryptUnsafe(input.bytes)
|
||||||
|
|
||||||
|
/** Decrypt the given bytes, expressing the result as a string.
|
||||||
|
*
|
||||||
|
* @param input
|
||||||
|
* The encrypted bytes.
|
||||||
|
* @param charset
|
||||||
|
* The character set used to express the decrypted bytes. Defaults to
|
||||||
|
* UTF-8.
|
||||||
|
* @return
|
||||||
|
* The decrypted string.
|
||||||
|
*/
|
||||||
|
def decryptToString(
|
||||||
|
input: RsaEncryptedBytes,
|
||||||
|
charset: Charset = StandardCharsets.UTF_8
|
||||||
|
): F[String] =
|
||||||
|
decrypt(input).map(bytes => new String(bytes, charset))
|
||||||
|
|
@ -0,0 +1,43 @@
|
||||||
|
package gs.smolban.auth
|
||||||
|
|
||||||
|
/** Represents arbitrary bytes that were encrypted using RSA.
|
||||||
|
*
|
||||||
|
* See:
|
||||||
|
*
|
||||||
|
* - [[RsaEncryption]]
|
||||||
|
* - [[RsaDecryption]]
|
||||||
|
*
|
||||||
|
* @param bytes
|
||||||
|
* The bytes encrypted using an RSA public key.
|
||||||
|
*/
|
||||||
|
final class RsaEncryptedBytes(val bytes: Array[Byte]):
|
||||||
|
/** @return
|
||||||
|
* These encrypted bytes, encoded using Base64.
|
||||||
|
*/
|
||||||
|
def encode(): Base64 = Base64.encode(bytes)
|
||||||
|
|
||||||
|
/** @inheritDocs
|
||||||
|
*/
|
||||||
|
override def equals(obj: Any): Boolean =
|
||||||
|
obj match
|
||||||
|
case other: RsaEncryptedBytes => bytes.sameElements(other.bytes)
|
||||||
|
case _ => false
|
||||||
|
|
||||||
|
/** @inheritDocs
|
||||||
|
*/
|
||||||
|
override def hashCode(): Int = bytes.hashCode()
|
||||||
|
|
||||||
|
object RsaEncryptedBytes:
|
||||||
|
|
||||||
|
/** Instantiate a new instance of [[RsaEncryptedBytes]] by base64-decoding the
|
||||||
|
* given input.
|
||||||
|
*
|
||||||
|
* @param value
|
||||||
|
* The value to decode.
|
||||||
|
* @return
|
||||||
|
* The new [[RsaEncryptedBytes]].
|
||||||
|
*/
|
||||||
|
def decode(value: Base64): RsaEncryptedBytes =
|
||||||
|
new RsaEncryptedBytes(value.decode())
|
||||||
|
|
||||||
|
end RsaEncryptedBytes
|
||||||
|
|
@ -0,0 +1,46 @@
|
||||||
|
package gs.smolban.auth
|
||||||
|
|
||||||
|
import cats.effect.Sync
|
||||||
|
import java.nio.charset.Charset
|
||||||
|
import java.nio.charset.StandardCharsets
|
||||||
|
import java.security.PublicKey
|
||||||
|
import javax.crypto.Cipher
|
||||||
|
|
||||||
|
/** Utility for public key encryption using RSA.
|
||||||
|
*
|
||||||
|
* Intended to be used in conjunction with [[RsaDecryption]].
|
||||||
|
*
|
||||||
|
* @param publicKey
|
||||||
|
* The public key used to encrypt data.
|
||||||
|
*/
|
||||||
|
final class RsaEncryption[F[_]: Sync](publicKey: PublicKey):
|
||||||
|
|
||||||
|
/** Encrypt the given bytes.
|
||||||
|
*
|
||||||
|
* @param input
|
||||||
|
* The data to encrypt.
|
||||||
|
* @return
|
||||||
|
* The encrypted representation of the input bytes.
|
||||||
|
*/
|
||||||
|
def encrypt(input: Array[Byte]): F[RsaEncryptedBytes] =
|
||||||
|
Sync[F].delay {
|
||||||
|
val cipher: Cipher = Cipher.getInstance(Rsa.CipherName)
|
||||||
|
val _ = cipher.init(Cipher.ENCRYPT_MODE, publicKey)
|
||||||
|
new RsaEncryptedBytes(cipher.doFinal(input))
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Encrypt the given string.
|
||||||
|
*
|
||||||
|
* @param input
|
||||||
|
* The data to encrypt.
|
||||||
|
* @param charset
|
||||||
|
* The character set used to extract bytes from the strong. Defaults to
|
||||||
|
* UTF-8.
|
||||||
|
* @return
|
||||||
|
* The encrypted representation of the input string.
|
||||||
|
*/
|
||||||
|
def encrypt(
|
||||||
|
input: String,
|
||||||
|
charset: Charset = StandardCharsets.UTF_8
|
||||||
|
): F[RsaEncryptedBytes] =
|
||||||
|
encrypt(input.getBytes(charset))
|
||||||
52
modules/auth/src/test/resources/rsa4096-private-key.key
Normal file
52
modules/auth/src/test/resources/rsa4096-private-key.key
Normal file
|
|
@ -0,0 +1,52 @@
|
||||||
|
-----BEGIN PRIVATE KEY-----
|
||||||
|
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
|
||||||
|
tXWSfxJaqr5e0HQj1LbBa+HOO4AvOQ==
|
||||||
|
-----END PRIVATE KEY-----
|
||||||
14
modules/auth/src/test/resources/rsa4096-public-key.crt
Normal file
14
modules/auth/src/test/resources/rsa4096-public-key.crt
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
-----BEGIN PUBLIC KEY-----
|
||||||
|
MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAtJTVkd8pysM18PmvOUI9
|
||||||
|
KqT6XdwklfmyWBrFxCWboyP1q2L0X1xZLql0hstiRlORsoQAM6Q/qlDkkiE7ekt4
|
||||||
|
T8yI8rKiHjiYi+tK1mlurkgH85+LOXuupB2mgKCg9EPLAdFdzMEOGKp4xXiEfxbV
|
||||||
|
MdjX2oLig+SZ/wZRtuJ/6slILXk1AXCxHl2+zJ/HOUCN8440r4uO9QgCo/ksZoHo
|
||||||
|
IxtNQOSVzPsiY/OduqQ5nBx9t5or2zYcPZu58HhUG26r1fEx0mjEtw7Ex7OWjhXm
|
||||||
|
vpQMU91jX4V1Tzu/6EbjPVp0afE1JyppSXFnlKoBFyPWbzzU7j9aBz1yeGGxhT+r
|
||||||
|
YSFBFv7oF6Bc++Lh4eVBoUdUAnGMCbnGVqpWv8y5khf7Etva2Is5SMSqEeuj0XIs
|
||||||
|
zpdaPUmHhq9LkG2ZFqqCWq1Wg/rxUnexmoPClE+evxw9rM7As2ENXY6lrd3/IwLu
|
||||||
|
UlH51SOCQl24xirNDtTJ5n1DF1hFPvucDQMdMrHP2PpyV6JPTz1eIzQRZu5VYtqe
|
||||||
|
oMhVh7nHrP5HwywlLYadIg28PWpJADRZGyYF3ArhWcn3nByDFmoIhBmi8PMdHAyH
|
||||||
|
qo86u85HQO0Br3PRf2xDtk7zcLQuX9cv5uOlgAfzeluiaSGW++wG08dOOnuVbay6
|
||||||
|
he8SwcpI1Hjw3ccmxQiZ2A0CAwEAAQ==
|
||||||
|
-----END PUBLIC KEY-----
|
||||||
63
modules/auth/src/test/scala/gs/smolban/auth/RsaTests.scala
Normal file
63
modules/auth/src/test/scala/gs/smolban/auth/RsaTests.scala
Normal file
|
|
@ -0,0 +1,63 @@
|
||||||
|
package gs.smolban.auth
|
||||||
|
|
||||||
|
import cats.effect.IO
|
||||||
|
import cats.effect.unsafe.IORuntime
|
||||||
|
import java.nio.file.Path
|
||||||
|
import munit.Location
|
||||||
|
|
||||||
|
class RsaTests extends munit.FunSuite:
|
||||||
|
import RsaTests.Resources
|
||||||
|
|
||||||
|
given IORuntime = IORuntime.global
|
||||||
|
|
||||||
|
def iotest(
|
||||||
|
name: String
|
||||||
|
)(
|
||||||
|
f: => IO[Unit]
|
||||||
|
)(
|
||||||
|
using
|
||||||
|
Location
|
||||||
|
): Unit =
|
||||||
|
test(name)(f.unsafeRunSync())
|
||||||
|
|
||||||
|
iotest(
|
||||||
|
"should encrypt and decrypt data, using keys sourced from a resource"
|
||||||
|
) {
|
||||||
|
val data = gs.uuid.v0.UUID.v7().withoutDashes()
|
||||||
|
for
|
||||||
|
encryption <- Rsa.initializeEncryptionFromResource[IO](
|
||||||
|
Resources.PublicKey
|
||||||
|
)
|
||||||
|
decryption <- Rsa.initializeDecryptionFromResource[IO](
|
||||||
|
Resources.PrivateKey
|
||||||
|
)
|
||||||
|
encrypted <- encryption.encrypt(data)
|
||||||
|
decrypted <- decryption.decryptToString(encrypted)
|
||||||
|
yield assertEquals(decrypted, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
iotest("should encrypt and decrypt data, using keys sourced from file") {
|
||||||
|
val data = gs.uuid.v0.UUID.v7().withoutDashes()
|
||||||
|
for
|
||||||
|
encryption <- Rsa.initializeEncryptionFromFile[IO](
|
||||||
|
Path.of(Resources.PublicKeyFile)
|
||||||
|
)
|
||||||
|
decryption <- Rsa.initializeDecryptionFromFile[IO](
|
||||||
|
Path.of(Resources.PrivateKeyFile)
|
||||||
|
)
|
||||||
|
encrypted <- encryption.encrypt(data)
|
||||||
|
decrypted <- decryption.decryptToString(encrypted)
|
||||||
|
yield assertEquals(decrypted, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
object RsaTests:
|
||||||
|
|
||||||
|
object Resources:
|
||||||
|
val BasePath: String = "modules/auth/src/test/resources"
|
||||||
|
val PublicKey: String = "rsa4096-public-key.crt"
|
||||||
|
val PublicKeyFile: String = s"$BasePath/$PublicKey"
|
||||||
|
val PrivateKey: String = "rsa4096-private-key.key"
|
||||||
|
val PrivateKeyFile: String = s"$BasePath/$PrivateKey"
|
||||||
|
end Resources
|
||||||
|
|
||||||
|
end RsaTests
|
||||||
Loading…
Add table
Reference in a new issue