-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
14 changed files
with
238 additions
and
2 deletions.
There are no files selected for viewing
30 changes: 30 additions & 0 deletions
30
backend/authik/src/main/kotlin/ru/ifmo/se/dating/authik/security/auth/JwtTokenIssuer.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
package ru.ifmo.se.dating.authik.security.auth | ||
|
||
import io.jsonwebtoken.Jwts | ||
import ru.ifmo.se.dating.security.auth.AccessToken | ||
import ru.ifmo.se.dating.security.auth.Jwt | ||
import java.security.PrivateKey | ||
import java.time.Clock | ||
import java.util.* | ||
import kotlin.time.toJavaDuration | ||
import java.time.Duration as JavaDuration | ||
import kotlin.time.Duration as KotlinDuration | ||
|
||
class JwtTokenIssuer( | ||
private val clock: Clock, | ||
private val privateSignKey: PrivateKey, | ||
duration: KotlinDuration, | ||
) : TokenIssuer { | ||
private val duration: JavaDuration = duration.toJavaDuration() | ||
|
||
override fun issue(payload: AccessToken.Payload): AccessToken { | ||
val now = clock.instant() | ||
return Jwts.builder() | ||
.claims(Jwt.serialize(payload)) | ||
.issuedAt(Date.from(now)) | ||
.expiration(Date.from(now + duration)) | ||
.signWith(privateSignKey) | ||
.compact() | ||
.let { AccessToken(it) } | ||
} | ||
} |
7 changes: 7 additions & 0 deletions
7
backend/authik/src/main/kotlin/ru/ifmo/se/dating/authik/security/auth/TokenIssuer.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
package ru.ifmo.se.dating.authik.security.auth | ||
|
||
import ru.ifmo.se.dating.security.auth.AccessToken | ||
|
||
interface TokenIssuer { | ||
fun issue(payload: AccessToken.Payload): AccessToken | ||
} |
35 changes: 35 additions & 0 deletions
35
backend/authik/src/test/kotlin/ru/ifmo/se/dating/authik/security/auth/JwtTest.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
package ru.ifmo.se.dating.authik.security.auth | ||
|
||
import org.junit.Assert.assertEquals | ||
import org.junit.Test | ||
import ru.ifmo.se.dating.security.auth.AccessToken | ||
import ru.ifmo.se.dating.security.auth.JwtTokenDecoder | ||
import ru.ifmo.se.dating.security.auth.User | ||
import java.security.KeyPairGenerator | ||
import java.time.Clock | ||
import kotlin.time.Duration.Companion.hours | ||
|
||
class JwtTest { | ||
@Test | ||
fun jwtLifecycle() { | ||
val rsa = KeyPairGenerator.getInstance("RSA").genKeyPair() | ||
|
||
val clock = Clock.systemUTC() | ||
|
||
val issuer = JwtTokenIssuer( | ||
clock = clock, | ||
privateSignKey = rsa.private, | ||
duration = 10.hours, | ||
) | ||
|
||
val decoder = JwtTokenDecoder( | ||
clock = clock, | ||
publicSignKey = rsa.public, | ||
) | ||
|
||
val expected = AccessToken.Payload(User.Id(123)) | ||
val actual = issuer.issue(expected).let { decoder.decode(it) } | ||
|
||
assertEquals(expected, actual) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
4 changes: 4 additions & 0 deletions
4
backend/foundation/src/main/kotlin/ru/ifmo/se/dating/exception/AuthenticationException.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
package ru.ifmo.se.dating.exception | ||
|
||
class AuthenticationException(message: String, cause: Throwable? = null) : | ||
SecurityException(message, cause) |
4 changes: 4 additions & 0 deletions
4
backend/foundation/src/main/kotlin/ru/ifmo/se/dating/exception/SecurityException.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
package ru.ifmo.se.dating.exception | ||
|
||
abstract class SecurityException(message: String, cause: Throwable? = null) : | ||
GenericException(message, cause) |
6 changes: 6 additions & 0 deletions
6
backend/foundation/src/main/kotlin/ru/ifmo/se/dating/security/auth/AccessToken.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
package ru.ifmo.se.dating.security.auth | ||
|
||
@JvmInline | ||
value class AccessToken(val text: String) { | ||
data class Payload(val userId: User.Id) | ||
} |
16 changes: 16 additions & 0 deletions
16
backend/foundation/src/main/kotlin/ru/ifmo/se/dating/security/auth/Jwt.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
package ru.ifmo.se.dating.security.auth | ||
|
||
import io.jsonwebtoken.Claims | ||
|
||
object Jwt { | ||
private const val USER_ID = "user_id" | ||
|
||
fun serialize(payload: AccessToken.Payload): Map<String, Any> = mapOf( | ||
USER_ID to payload.userId.number, | ||
) | ||
|
||
fun deserialize(claims: Claims): AccessToken.Payload { | ||
val userId = User.Id(claims[USER_ID]!! as Int) | ||
return AccessToken.Payload(userId) | ||
} | ||
} |
30 changes: 30 additions & 0 deletions
30
backend/foundation/src/main/kotlin/ru/ifmo/se/dating/security/auth/JwtTokenDecoder.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
package ru.ifmo.se.dating.security.auth | ||
|
||
import io.jsonwebtoken.ExpiredJwtException | ||
import io.jsonwebtoken.Jwts | ||
import io.jsonwebtoken.MalformedJwtException | ||
import ru.ifmo.se.dating.exception.AuthenticationException | ||
import java.security.PublicKey | ||
import java.time.Clock | ||
import java.util.* | ||
import javax.crypto.SecretKey | ||
|
||
class JwtTokenDecoder( | ||
private val clock: Clock, | ||
private val publicSignKey: PublicKey, | ||
) : TokenDecoder { | ||
override fun decode(token: AccessToken): AccessToken.Payload = | ||
try { | ||
Jwts.parser() | ||
.verifyWith(publicSignKey) | ||
.clock { Date.from(clock.instant()) } | ||
.build() | ||
.parseSignedClaims(token.text) | ||
.payload | ||
.let { Jwt.deserialize(it) } | ||
} catch (e: ExpiredJwtException) { | ||
throw AuthenticationException(e.message!!, e) | ||
} catch (e: MalformedJwtException) { | ||
throw AuthenticationException(e.message!!, e) | ||
} | ||
} |
5 changes: 5 additions & 0 deletions
5
backend/foundation/src/main/kotlin/ru/ifmo/se/dating/security/auth/TokenDecoder.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
package ru.ifmo.se.dating.security.auth | ||
|
||
interface TokenDecoder { | ||
fun decode(token: AccessToken): AccessToken.Payload | ||
} |
12 changes: 12 additions & 0 deletions
12
backend/foundation/src/main/kotlin/ru/ifmo/se/dating/security/auth/User.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
package ru.ifmo.se.dating.security.auth | ||
|
||
import ru.ifmo.se.dating.validation.expectId | ||
|
||
object User { | ||
@JvmInline | ||
value class Id(val number: Int) { | ||
init { | ||
expectId(number) | ||
} | ||
} | ||
} |
41 changes: 41 additions & 0 deletions
41
backend/foundation/src/main/kotlin/ru/ifmo/se/dating/security/key/Keys.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
package ru.ifmo.se.dating.security.key | ||
|
||
import java.security.Key | ||
import java.security.KeyFactory | ||
import java.security.PrivateKey | ||
import java.security.PublicKey | ||
import java.security.spec.PKCS8EncodedKeySpec | ||
import java.security.spec.X509EncodedKeySpec | ||
import java.util.* | ||
import javax.crypto.SecretKey | ||
import javax.crypto.spec.SecretKeySpec | ||
|
||
object Keys { | ||
fun serialize(key: Key): String = | ||
"${key.algorithm}:${Base64.getEncoder().encodeToString(key.encoded)}" | ||
|
||
fun deserializeSecret(string: String): SecretKey { | ||
val (algorithm, encodedKey) = split(string) | ||
return SecretKeySpec(encodedKey, algorithm) | ||
} | ||
|
||
fun deserializePublic(string: String): PublicKey { | ||
val (algorithm, encodedKey) = split(string) | ||
return KeyFactory.getInstance(algorithm).generatePublic(X509EncodedKeySpec(encodedKey)) | ||
} | ||
|
||
fun deserializePrivate(string: String): PrivateKey { | ||
val (algorithm, encodedKey) = split(string) | ||
return KeyFactory.getInstance(algorithm).generatePrivate(PKCS8EncodedKeySpec(encodedKey)) | ||
} | ||
|
||
private fun split(string: String): Pair<String, ByteArray> { | ||
val parts = string.split(":") | ||
if (parts.size != 2) { | ||
throw IllegalArgumentException("Invalid serialized key format") | ||
} | ||
val algorithm = parts[0] | ||
val encodedKey = parts[1] | ||
return algorithm to Base64.getDecoder().decode(encodedKey) | ||
} | ||
} |
35 changes: 35 additions & 0 deletions
35
backend/foundation/src/test/kotlin/ru/ifmo/se/dating/security/key/KeysTest.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
package ru.ifmo.se.dating.security.key | ||
|
||
import org.junit.Assert.assertEquals | ||
import org.junit.Test | ||
import java.security.KeyPairGenerator | ||
import javax.crypto.KeyGenerator | ||
|
||
class KeysTest { | ||
@Test | ||
fun secretRoundTrip() { | ||
for (i in 0..32) { | ||
val key = KeyGenerator.getInstance("AES").generateKey() | ||
assertEquals(key, Keys.serialize(key).let { Keys.deserializeSecret(it) }) | ||
} | ||
} | ||
|
||
@Test | ||
fun publicRoundTrip() { | ||
for (i in 0..8) { | ||
val pair = KeyPairGenerator.getInstance("RSA").genKeyPair() | ||
assertEquals(pair.public, Keys.serialize(pair.public).let { Keys.deserializePublic(it) }) | ||
assertEquals(pair.private, Keys.serialize(pair.private).let { Keys.deserializePrivate(it) }) | ||
} | ||
} | ||
|
||
@Test | ||
fun generateKeys() { | ||
val aes = KeyGenerator.getInstance("AES").generateKey() | ||
val rsa = KeyPairGenerator.getInstance("RSA").genKeyPair() | ||
|
||
println("AES Secret: '${Keys.serialize(aes)}'") | ||
println("RSA Public: '${Keys.serialize(rsa.public)}'") | ||
println("RSA Private: '${Keys.serialize(rsa.private)}'") | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters