Add authorization endpoint (#284)
This commit is contained in:
@@ -98,6 +98,8 @@ dependencies {
|
||||
implementation libs.caffeine
|
||||
implementation libs.javax.annotations
|
||||
|
||||
implementation libs.auth0.jwt
|
||||
|
||||
testImplementation libs.cglib.nodep
|
||||
testImplementation libs.spockframework.core
|
||||
testImplementation libs.grpc.testing
|
||||
|
||||
Submodule emerald-grpc updated: 55af14cf34...2c7c3286a5
@@ -124,6 +124,8 @@ spring-cloud-starter-sleuth = "org.springframework.cloud:spring-cloud-starter-sl
|
||||
spring-cloud-sleuth-zipkin = "org.springframework.cloud:spring-cloud-sleuth-zipkin:3.1.6"
|
||||
brave-instrumentation-grpc = "io.zipkin.brave:brave-instrumentation-grpc:5.15.0"
|
||||
|
||||
auth0-jwt = "com.auth0:java-jwt:4.4.0"
|
||||
|
||||
[bundles]
|
||||
apache-commons = ["commons-io", "apache-commons-lang3", "apache-commons-collections4"]
|
||||
etherjar = ["etherjar-domain", "etherjar-hex", "etherjar-rpc-api", "etherjar-rpc-http", "etherjar-rpc-ws", "etherjar-tx", "etherjar-contract", "etherjar-erc20"]
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
*/
|
||||
package io.emeraldpay.dshackle
|
||||
|
||||
import io.emeraldpay.dshackle.config.AuthorizationConfig
|
||||
import io.emeraldpay.dshackle.config.CacheConfig
|
||||
import io.emeraldpay.dshackle.config.ChainsConfig
|
||||
import io.emeraldpay.dshackle.config.CompressionConfig
|
||||
@@ -153,4 +154,9 @@ open class Config(
|
||||
open fun chainsConfig(mainConfig: MainConfig): ChainsConfig {
|
||||
return mainConfig.chains
|
||||
}
|
||||
|
||||
@Bean
|
||||
open fun authorizationConfig(mainConfig: MainConfig): AuthorizationConfig {
|
||||
return mainConfig.authorization
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
*/
|
||||
package io.emeraldpay.dshackle
|
||||
|
||||
import io.emeraldpay.dshackle.auth.AuthInterceptor
|
||||
import io.emeraldpay.dshackle.config.MainConfig
|
||||
import io.emeraldpay.dshackle.monitoring.accesslog.AccessHandlerGrpc
|
||||
import io.grpc.Codec
|
||||
@@ -29,8 +30,6 @@ import io.micrometer.core.instrument.Metrics
|
||||
import io.micrometer.core.instrument.Tag
|
||||
import io.micrometer.core.instrument.binder.jvm.ExecutorServiceMetrics
|
||||
import org.slf4j.LoggerFactory
|
||||
import org.springframework.beans.factory.annotation.Autowired
|
||||
import org.springframework.beans.factory.annotation.Qualifier
|
||||
import org.springframework.beans.factory.annotation.Value
|
||||
import org.springframework.scheduling.concurrent.CustomizableThreadFactory
|
||||
import org.springframework.stereotype.Service
|
||||
@@ -46,9 +45,7 @@ open class GrpcServer(
|
||||
private val tlsSetup: TlsSetup,
|
||||
private val accessHandler: AccessHandlerGrpc,
|
||||
private val grpcServerBraveInterceptor: ServerInterceptor,
|
||||
@Autowired(required = false)
|
||||
@Qualifier("serverSpansInterceptor")
|
||||
private val serverSpansInterceptor: ServerInterceptor?
|
||||
private val authInterceptor: AuthInterceptor
|
||||
) {
|
||||
@Value("\${spring.application.max-metadata-size}")
|
||||
private var maxMetadataSize: Int = Defaults.maxMetadataSize
|
||||
@@ -87,9 +84,9 @@ open class GrpcServer(
|
||||
}
|
||||
|
||||
serverBuilder.intercept(grpcServerBraveInterceptor)
|
||||
serverSpansInterceptor?.let {
|
||||
serverBuilder.intercept(it)
|
||||
log.info("Collect spans from provider is enabled")
|
||||
if (mainConfig.authorization.enabled) {
|
||||
serverBuilder.intercept(authInterceptor)
|
||||
log.info("Token authorization is turned on")
|
||||
}
|
||||
|
||||
tlsSetup.setupServer("Native gRPC", mainConfig.tls, true)?.let {
|
||||
|
||||
25
src/main/kotlin/io/emeraldpay/dshackle/auth/AuthContext.kt
Normal file
25
src/main/kotlin/io/emeraldpay/dshackle/auth/AuthContext.kt
Normal file
@@ -0,0 +1,25 @@
|
||||
package io.emeraldpay.dshackle.auth
|
||||
|
||||
import java.time.Instant
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
|
||||
class AuthContext {
|
||||
|
||||
companion object {
|
||||
val sessions = ConcurrentHashMap<String, TokenWrapper>()
|
||||
|
||||
fun putTokenInContext(tokenWrapper: TokenWrapper) {
|
||||
sessions[tokenWrapper.sessionId] = tokenWrapper
|
||||
}
|
||||
|
||||
fun removeToken(sessionId: String) {
|
||||
sessions.remove(sessionId)
|
||||
}
|
||||
}
|
||||
|
||||
data class TokenWrapper(
|
||||
val token: String,
|
||||
val issuedAt: Instant,
|
||||
val sessionId: String
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package io.emeraldpay.dshackle.auth
|
||||
|
||||
import io.emeraldpay.dshackle.auth.processor.SESSION_ID
|
||||
import io.grpc.Metadata
|
||||
import io.grpc.Metadata.ASCII_STRING_MARSHALLER
|
||||
import io.grpc.ServerCall
|
||||
import io.grpc.ServerCallHandler
|
||||
import io.grpc.ServerInterceptor
|
||||
import io.grpc.Status
|
||||
import org.springframework.stereotype.Component
|
||||
|
||||
const val AUTH_METHOD_NAME = "emerald.Auth/Authenticate"
|
||||
const val REFLECT_METHOD_NAME = "grpc.reflection.v1alpha.ServerReflection/ServerReflectionInfo"
|
||||
|
||||
@Component
|
||||
class AuthInterceptor : ServerInterceptor {
|
||||
private val specialMethods = setOf(AUTH_METHOD_NAME, REFLECT_METHOD_NAME)
|
||||
|
||||
override fun <ReqT : Any, RespT : Any> interceptCall(
|
||||
call: ServerCall<ReqT, RespT>,
|
||||
headers: Metadata,
|
||||
next: ServerCallHandler<ReqT, RespT>
|
||||
): ServerCall.Listener<ReqT> {
|
||||
val sessionId = headers.get(
|
||||
Metadata.Key.of(SESSION_ID, ASCII_STRING_MARSHALLER)
|
||||
)
|
||||
val isOrdinaryMethod = !specialMethods.contains(call.methodDescriptor.fullMethodName)
|
||||
|
||||
if (isOrdinaryMethod && (sessionId == null || !AuthContext.sessions.containsKey(sessionId))) {
|
||||
val cause = if (sessionId == null) "sessionId is not passed" else "Session $sessionId does not exist"
|
||||
throw Status.UNAUTHENTICATED
|
||||
.withDescription(cause)
|
||||
.asException()
|
||||
}
|
||||
|
||||
return next.startCall(call, headers)
|
||||
}
|
||||
}
|
||||
47
src/main/kotlin/io/emeraldpay/dshackle/auth/AuthRpc.kt
Normal file
47
src/main/kotlin/io/emeraldpay/dshackle/auth/AuthRpc.kt
Normal file
@@ -0,0 +1,47 @@
|
||||
package io.emeraldpay.dshackle.auth
|
||||
|
||||
import io.emeraldpay.api.proto.AuthOuterClass
|
||||
import io.emeraldpay.api.proto.ReactorAuthGrpc
|
||||
import io.emeraldpay.dshackle.auth.service.AuthService
|
||||
import io.grpc.Status
|
||||
import io.grpc.StatusException
|
||||
import org.slf4j.LoggerFactory
|
||||
import org.springframework.stereotype.Service
|
||||
import reactor.core.publisher.Mono
|
||||
import reactor.core.scheduler.Scheduler
|
||||
|
||||
@Service
|
||||
class AuthRpc(
|
||||
private val authService: AuthService,
|
||||
private val authScheduler: Scheduler
|
||||
) : ReactorAuthGrpc.AuthImplBase() {
|
||||
|
||||
companion object {
|
||||
private val log = LoggerFactory.getLogger(AuthRpc::class.java)
|
||||
}
|
||||
|
||||
override fun authenticate(request: Mono<AuthOuterClass.AuthRequest>): Mono<AuthOuterClass.AuthResponse> {
|
||||
log.info("Start auth process...")
|
||||
return request
|
||||
.subscribeOn(authScheduler)
|
||||
.map {
|
||||
val token = authService.authenticate(it.token)
|
||||
AuthOuterClass.AuthResponse.newBuilder()
|
||||
.setProviderToken(token)
|
||||
.build()
|
||||
}.onErrorResume {
|
||||
if (it is StatusException) {
|
||||
log.error(it.message)
|
||||
Mono.error(it)
|
||||
} else {
|
||||
val message = "Internal error: ${it.message}"
|
||||
log.error(message, it)
|
||||
Mono.error(
|
||||
Status.INTERNAL
|
||||
.withDescription(message)
|
||||
.asException()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
package io.emeraldpay.dshackle.auth.processor
|
||||
|
||||
import com.auth0.jwt.JWT
|
||||
import com.auth0.jwt.JWTVerifier
|
||||
import com.auth0.jwt.algorithms.Algorithm
|
||||
import io.emeraldpay.dshackle.auth.AuthContext
|
||||
import io.emeraldpay.dshackle.auth.service.KeyReader
|
||||
import io.emeraldpay.dshackle.config.AuthorizationConfig
|
||||
import io.grpc.Status
|
||||
import org.springframework.stereotype.Component
|
||||
import java.security.PrivateKey
|
||||
import java.security.PublicKey
|
||||
import java.security.interfaces.RSAPrivateKey
|
||||
import java.security.interfaces.RSAPublicKey
|
||||
import java.time.Instant
|
||||
import java.util.UUID
|
||||
|
||||
const val SESSION_ID = "sessionId"
|
||||
const val VERSION = "version"
|
||||
|
||||
enum class AuthVersion {
|
||||
V1;
|
||||
|
||||
companion object {
|
||||
fun getVersion(version: String) = values().find { it.name == version }
|
||||
?: throw Status.INVALID_ARGUMENT
|
||||
.withDescription("Unsupported auth version $version")
|
||||
.asException()
|
||||
}
|
||||
}
|
||||
|
||||
abstract class AuthProcessor(
|
||||
private val authorizationConfig: AuthorizationConfig
|
||||
) {
|
||||
|
||||
open fun process(keys: KeyReader.Keys, token: String): AuthContext.TokenWrapper {
|
||||
try {
|
||||
val verifier: JWTVerifier = JWT.require(verifyingAlgorithm(keys.externalPublicKey))
|
||||
.withIssuer(authorizationConfig.publicKeyOwner)
|
||||
.build()
|
||||
verifier.verify(token)
|
||||
} catch (e: Exception) {
|
||||
throw Status.INVALID_ARGUMENT
|
||||
.withDescription("Invalid token: ${e.message}")
|
||||
.asException()
|
||||
}
|
||||
|
||||
return processInternal(keys.providerPrivateKey)
|
||||
}
|
||||
|
||||
protected abstract fun processInternal(privateKey: PrivateKey): AuthContext.TokenWrapper
|
||||
|
||||
protected abstract fun verifyingAlgorithm(publicKey: PublicKey): Algorithm
|
||||
}
|
||||
|
||||
@Component
|
||||
open class AuthProcessorV1(
|
||||
authorizationConfig: AuthorizationConfig
|
||||
) : AuthProcessor(authorizationConfig) {
|
||||
|
||||
override fun processInternal(privateKey: PrivateKey): AuthContext.TokenWrapper {
|
||||
val issAt = Instant.now()
|
||||
val sessionId = UUID.randomUUID().toString()
|
||||
val token = JWT.create()
|
||||
.withIssuedAt(issAt)
|
||||
.withClaim(SESSION_ID, sessionId)
|
||||
.withClaim(VERSION, AuthVersion.V1.toString())
|
||||
.sign(Algorithm.RSA256(privateKey as RSAPrivateKey))
|
||||
|
||||
return AuthContext.TokenWrapper(token, issAt, sessionId)
|
||||
}
|
||||
|
||||
override fun verifyingAlgorithm(publicKey: PublicKey): Algorithm =
|
||||
Algorithm.RSA256(publicKey as RSAPublicKey, null)
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package io.emeraldpay.dshackle.auth.processor
|
||||
|
||||
import com.auth0.jwt.interfaces.DecodedJWT
|
||||
import io.grpc.Status
|
||||
import org.slf4j.LoggerFactory
|
||||
import org.springframework.stereotype.Component
|
||||
|
||||
@Component
|
||||
class AuthProcessorResolver(
|
||||
private val authProcessorV1: AuthProcessor
|
||||
) {
|
||||
|
||||
companion object {
|
||||
private val log = LoggerFactory.getLogger(AuthProcessorResolver::class.java)
|
||||
}
|
||||
|
||||
fun getAuthProcessor(token: DecodedJWT): AuthProcessor {
|
||||
val claimVersion = token.getClaim(VERSION)
|
||||
if (claimVersion.isMissing) {
|
||||
throw Status.INVALID_ARGUMENT
|
||||
.withDescription("Version is not specified in the token")
|
||||
.asException()
|
||||
}
|
||||
|
||||
val version = AuthVersion.getVersion(claimVersion.asString())
|
||||
log.info("Using $version of authentication")
|
||||
|
||||
if (version == AuthVersion.V1) {
|
||||
return authProcessorV1
|
||||
}
|
||||
|
||||
throw Status.INVALID_ARGUMENT
|
||||
.withDescription("Unsupported auth version $version")
|
||||
.asException()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package io.emeraldpay.dshackle.auth.processor
|
||||
|
||||
import io.emeraldpay.dshackle.auth.AuthContext
|
||||
import org.slf4j.LoggerFactory
|
||||
import org.springframework.scheduling.annotation.Scheduled
|
||||
import org.springframework.stereotype.Component
|
||||
import java.time.Instant
|
||||
import java.time.temporal.ChronoUnit
|
||||
|
||||
@Component
|
||||
open class TokenProcessor {
|
||||
|
||||
companion object {
|
||||
private val log = LoggerFactory.getLogger(TokenProcessor::class.java)
|
||||
}
|
||||
|
||||
@Scheduled(fixedRate = 30000)
|
||||
fun invalidateTokens() {
|
||||
AuthContext.sessions
|
||||
.filter { Instant.now().isAfter(it.value.issuedAt.plus(1, ChronoUnit.HOURS)) }
|
||||
.forEach {
|
||||
log.info("Invalidate token with sessionId ${it.key}")
|
||||
AuthContext.removeToken(it.key)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package io.emeraldpay.dshackle.auth.service
|
||||
|
||||
import com.auth0.jwt.JWT
|
||||
import io.emeraldpay.dshackle.auth.AuthContext
|
||||
import io.emeraldpay.dshackle.auth.processor.AuthProcessorResolver
|
||||
import io.emeraldpay.dshackle.config.AuthorizationConfig
|
||||
import io.grpc.Status
|
||||
import org.springframework.stereotype.Service
|
||||
|
||||
@Service
|
||||
class AuthService(
|
||||
private val authorizationConfig: AuthorizationConfig,
|
||||
private val rsaKeyReader: KeyReader,
|
||||
private val authProcessorResolver: AuthProcessorResolver
|
||||
) {
|
||||
|
||||
fun authenticate(token: String): String {
|
||||
if (!authorizationConfig.enabled) {
|
||||
throw Status.UNIMPLEMENTED
|
||||
.withDescription("Authentication process is not enabled")
|
||||
.asException()
|
||||
}
|
||||
|
||||
val keys = rsaKeyReader.getKeyPair(
|
||||
authorizationConfig.providerPrivateKeyPath, authorizationConfig.externalPublicKeyPath
|
||||
)
|
||||
val decodedJwt = JWT.decode(token)
|
||||
|
||||
return authProcessorResolver
|
||||
.getAuthProcessor(decodedJwt)
|
||||
.process(keys, token)
|
||||
.run {
|
||||
AuthContext.putTokenInContext(this)
|
||||
this.token
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package io.emeraldpay.dshackle.auth.service
|
||||
|
||||
import java.security.PrivateKey
|
||||
import java.security.PublicKey
|
||||
|
||||
interface KeyReader {
|
||||
|
||||
fun getKeyPair(providerPrivateKeyPath: String, externalPublicKeyPath: String): Keys
|
||||
|
||||
data class Keys(
|
||||
val providerPrivateKey: PrivateKey,
|
||||
val externalPublicKey: PublicKey
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package io.emeraldpay.dshackle.auth.service
|
||||
|
||||
import org.bouncycastle.openssl.PEMParser
|
||||
import org.springframework.stereotype.Component
|
||||
import java.io.StringReader
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.Paths
|
||||
import java.security.KeyFactory
|
||||
import java.security.spec.PKCS8EncodedKeySpec
|
||||
import java.security.spec.X509EncodedKeySpec
|
||||
|
||||
@Component
|
||||
class RsaKeyReader : KeyReader {
|
||||
private val factory = KeyFactory.getInstance("RSA")
|
||||
|
||||
override fun getKeyPair(providerPrivateKeyPath: String, externalPublicKeyPath: String): KeyReader.Keys {
|
||||
val privateKeyReader = StringReader(Files.readString(Paths.get(providerPrivateKeyPath)))
|
||||
val publicKeyReader = StringReader(Files.readString(Paths.get(externalPublicKeyPath)))
|
||||
|
||||
val privatePem = PEMParser(privateKeyReader).readPemObject()
|
||||
val publicPem = PEMParser(publicKeyReader).readPemObject()
|
||||
|
||||
val privateKeySpec = PKCS8EncodedKeySpec(privatePem.content)
|
||||
val publicKeySpec = X509EncodedKeySpec(publicPem.content)
|
||||
|
||||
val pubKey = factory.generatePublic(publicKeySpec)
|
||||
val privateKey = factory.generatePrivate(privateKeySpec)
|
||||
|
||||
return KeyReader.Keys(
|
||||
privateKey,
|
||||
pubKey
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package io.emeraldpay.dshackle.config
|
||||
|
||||
data class AuthorizationConfig(
|
||||
val enabled: Boolean,
|
||||
val publicKeyOwner: String,
|
||||
val providerPrivateKeyPath: String,
|
||||
val externalPublicKeyPath: String
|
||||
) {
|
||||
|
||||
companion object {
|
||||
@JvmStatic
|
||||
fun default() = AuthorizationConfig(false, "", "", "")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package io.emeraldpay.dshackle.config
|
||||
|
||||
import org.slf4j.LoggerFactory
|
||||
import org.springframework.util.ResourceUtils
|
||||
import org.yaml.snakeyaml.nodes.MappingNode
|
||||
import java.io.FileNotFoundException
|
||||
|
||||
class AuthorizationConfigReader : YamlConfigReader<AuthorizationConfig>() {
|
||||
|
||||
companion object {
|
||||
private val log = LoggerFactory.getLogger(AuthorizationConfigReader::class.java)
|
||||
}
|
||||
|
||||
override fun read(input: MappingNode?): AuthorizationConfig {
|
||||
val auth = getMapping(input, "auth")
|
||||
if (auth == null) {
|
||||
log.warn("Authorization is not using")
|
||||
return AuthorizationConfig.default()
|
||||
}
|
||||
|
||||
val enabled = getValueAsBool(auth, "enabled")
|
||||
if (enabled == null || !enabled) {
|
||||
log.warn("Authorization is not enabled")
|
||||
return AuthorizationConfig.default()
|
||||
}
|
||||
|
||||
val publicKeyOwner = getValueAsString(auth, "publicKeyOwner")
|
||||
?: throw IllegalStateException("Public key owner in not specified")
|
||||
|
||||
val keyPair = getMapping(auth, "keys") ?: throw IllegalStateException("Auth keys is not specified")
|
||||
val privateKey = getValueAsString(keyPair, "provider-private-key")
|
||||
?: throw IllegalStateException("Private key in not specified")
|
||||
val publicKey = getValueAsString(keyPair, "external-public-key")
|
||||
?: throw IllegalStateException("External key in not specified")
|
||||
|
||||
if (fileNotExists(privateKey)) {
|
||||
throw IllegalStateException("There is no such file: $privateKey")
|
||||
}
|
||||
if (fileNotExists(publicKey)) {
|
||||
throw IllegalStateException("There is no such file: $publicKey")
|
||||
}
|
||||
|
||||
return AuthorizationConfig(enabled, publicKeyOwner, privateKey, publicKey)
|
||||
}
|
||||
|
||||
private fun fileNotExists(path: String): Boolean {
|
||||
return try {
|
||||
!ResourceUtils.getFile(path).exists()
|
||||
} catch (e: FileNotFoundException) {
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -30,4 +30,5 @@ class MainConfig {
|
||||
var signature: SignatureConfig? = null
|
||||
var compression: CompressionConfig = CompressionConfig.default()
|
||||
var chains: ChainsConfig = ChainsConfig.default()
|
||||
var authorization: AuthorizationConfig = AuthorizationConfig.default()
|
||||
}
|
||||
|
||||
@@ -33,6 +33,7 @@ class MainConfigReader(
|
||||
private val signatureConfigReader = SignatureConfigReader(fileResolver)
|
||||
private val compressionConfigReader = CompressionConfigReader()
|
||||
private val chainsConfigReader = ChainsConfigReader(upstreamsConfigReader)
|
||||
private val authorizationConfigReader = AuthorizationConfigReader()
|
||||
|
||||
override fun read(input: MappingNode?): MainConfig {
|
||||
val config = MainConfig()
|
||||
@@ -80,6 +81,9 @@ class MainConfigReader(
|
||||
chainsConfigReader.read(input).let {
|
||||
config.chains = it
|
||||
}
|
||||
authorizationConfigReader.read(input).let {
|
||||
config.authorization = it
|
||||
}
|
||||
return config
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,6 +40,11 @@ open class SchedulersConfig {
|
||||
return makePool("grpc-client-channel", 10, monitoringConfig)
|
||||
}
|
||||
|
||||
@Bean
|
||||
open fun authScheduler(monitoringConfig: MonitoringConfig): Scheduler {
|
||||
return makeScheduler("auth-scheduler", 4, monitoringConfig)
|
||||
}
|
||||
|
||||
private fun makeScheduler(name: String, size: Int, monitoringConfig: MonitoringConfig): Scheduler {
|
||||
return Schedulers.fromExecutorService(makePool(name, size, monitoringConfig))
|
||||
}
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
package io.emeraldpay.dshackle.config
|
||||
|
||||
import spock.lang.Specification
|
||||
|
||||
class AuthorizationConfigReaderSpec extends Specification {
|
||||
def reader = new AuthorizationConfigReader()
|
||||
|
||||
def "if no auth option then default settings"() {
|
||||
setup:
|
||||
def yamlIs = this.class.getClassLoader().getResourceAsStream("configs/upstreams-basic.yaml")
|
||||
when:
|
||||
def act = reader.read(yamlIs)
|
||||
then:
|
||||
act == AuthorizationConfig.default()
|
||||
}
|
||||
|
||||
def "if auth is disabled then defaults settings"() {
|
||||
setup:
|
||||
def yamlIs = this.class.getClassLoader().getResourceAsStream("configs/auth-disabled.yaml")
|
||||
when:
|
||||
def act = reader.read(yamlIs)
|
||||
then:
|
||||
act == AuthorizationConfig.default()
|
||||
}
|
||||
|
||||
def "check default settings"() {
|
||||
when:
|
||||
def act = AuthorizationConfig.default()
|
||||
then:
|
||||
!act.enabled
|
||||
act.externalPublicKeyPath == ""
|
||||
act.providerPrivateKeyPath == ""
|
||||
}
|
||||
|
||||
def "exceptions if no settings"() {
|
||||
setup:
|
||||
def yamlIs = this.class.getClassLoader().getResourceAsStream(filePath)
|
||||
when:
|
||||
reader.read(yamlIs)
|
||||
then:
|
||||
def t = thrown(IllegalStateException)
|
||||
t.message == message
|
||||
where:
|
||||
filePath | message
|
||||
"configs/auth-without-public-key.yaml" | "External key in not specified"
|
||||
"configs/auth-without-private-key.yaml" | "Private key in not specified"
|
||||
"configs/auth-without-key-pair.yaml" | "Auth keys is not specified"
|
||||
"configs/auth-without-key-owner.yaml" | "Public key owner in not specified"
|
||||
"configs/auth-with-wrong-priv-key.yaml" | "There is no such file: classpath:keys/priv-wrong.p8.key"
|
||||
"configs/auth-with-wrong-pub-key.yaml" | "There is no such file: classpath:keys/pub-wrong.key"
|
||||
}
|
||||
}
|
||||
@@ -102,5 +102,11 @@ class MainConfigReaderSpec extends Specification {
|
||||
id == "infura"
|
||||
}
|
||||
}
|
||||
act.authorization != null
|
||||
with(act.authorization) {
|
||||
enabled
|
||||
providerPrivateKeyPath == "classpath:keys/priv.p8.key"
|
||||
externalPublicKeyPath == "classpath:keys/public.pem"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
package io.emeraldpay.dshackle.auth.processor
|
||||
|
||||
import com.auth0.jwt.JWT
|
||||
import io.emeraldpay.dshackle.config.AuthorizationConfig
|
||||
import io.grpc.StatusException
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
import org.junit.jupiter.api.Assertions.assertThrows
|
||||
import org.junit.jupiter.api.Assertions.assertTrue
|
||||
import org.junit.jupiter.api.Test
|
||||
|
||||
class AuthProcessorResolverTest {
|
||||
private val authProcessorV1 = AuthProcessorV1(AuthorizationConfig.default())
|
||||
private val authProcessorResolver = AuthProcessorResolver(authProcessorV1)
|
||||
|
||||
@Test
|
||||
fun `get processor of V1 version`() {
|
||||
val token = JWT.decode(
|
||||
"eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJkcnBjIiwiaWF0IjoxNjkyMTg1OTMxLCJ2ZXJzaW9uI" +
|
||||
"joiVjEifQ.BZILN0GQ7JzXGFz-GZIbFTT9E5L-miB4Nga0v4o_cQThk8gbDelBRzEfdsqxCq_ppPr3v_Own8M-vR9yQElx5nEdlI4xe5QAMdIvr3g" +
|
||||
"12fMckydX9IsW4sVQ1kJJY8RrHb-WL-uI0WSWqoMSwf-Psb-UyiEHAjc3oK7fA72lBaGT4waPHOxRBPvezwg7N934vCZvZMAftFfVgmeEtbCeD7bF" +
|
||||
"umEr0uEmkIKPTg4QwP-VMvqoLBYpMiJVzP_Ipg_wRHJ7fUN0BGEPjjMvhQ_6TWByiQUBz1kTMd0Ebf_kEuXFQeiwA-FXHJpWczzh66CbbmmWAWsi" +
|
||||
"ehKw3KPZeBj0oQ"
|
||||
)
|
||||
val processor = authProcessorResolver.getAuthProcessor(token)
|
||||
|
||||
assertTrue(processor is AuthProcessorV1)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `failed if no version is in a token`() {
|
||||
val token = JWT.decode(
|
||||
"eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJkcnBjIiwiaWF0IjoxNjkyMTg5NTUzfQ.sQ1Q3DFC7kOlHWdnWaxv2F" +
|
||||
"vhGso6ajJQVWtrE1hYmL_AIH_Lz8LNpqFShlL2itEGfmyRz5-Vrbdf-yWjeyLyNDlf7pay2lNE5Pvm2-EtQd8GUYcCFFIz7Sxc2Iphe2" +
|
||||
"YIx6kBwSlaR0RXBcmUlOtKrnON0bBNzSojBmtCT7-j4hTpoKhYr04Fr9EJWHfw7grVZjU8rEizAX_SR3ZNoufjK_pZaIyI9qUKVPYSepP" +
|
||||
"lXtQzVjA80qSeYpkeFCOLwlQD_yTArDNWlwe7-CthtBOAtctoTMwyudfJezT2ilXrigzbauzU5BEi1cNxacHpjNuXhyY0TiacJGugWfRgaaGy6g"
|
||||
)
|
||||
|
||||
val e = assertThrows(StatusException::class.java) { authProcessorResolver.getAuthProcessor(token) }
|
||||
assertEquals("INVALID_ARGUMENT: Version is not specified in the token", e.message)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `failed if the wrong version is specified`() {
|
||||
val token = JWT.decode(
|
||||
"eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJkcnBjIiwiaWF0IjoxNjkyMTg5OTI3LCJ2ZXJzaW9uIjoiVjIifQ.pT" +
|
||||
"mG3Z-h9P1DlocTKZ3BpAKNdsvRGjXo71I0GUpUemAnauUaH-OgxUOHlNcZ2uB4f1poEWsbpExeuZ15YAiBf2WBCEV6J6xH1u0cPC1O-8" +
|
||||
"hHNb46166ngzo-BtZ7Rn7FeVEayyICslc9iXM5GvoFJyJIdn9uLMzalyDJq2bUmIelym4edkQ3ybF-pqf8garuVVErnAsbOFXbYQlfO5ZJ" +
|
||||
"4zq6PfJo7QSkreMzQ4tK_-JJIGG-EK1bQjsAD7JiXipY2cJY17VlHuavI0DfcJlOe-QggbTH63rxL6JvXCyFux7gI7zdqSBP1fNDJNzTLA" +
|
||||
"cnR7jCN0kMIa8urQgdePZyRg"
|
||||
)
|
||||
|
||||
val e = assertThrows(StatusException::class.java) { authProcessorResolver.getAuthProcessor(token) }
|
||||
assertEquals("INVALID_ARGUMENT: Unsupported auth version V2", e.message)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
package io.emeraldpay.dshackle.auth.processor
|
||||
|
||||
import com.auth0.jwt.JWT
|
||||
import com.auth0.jwt.JWTVerifier
|
||||
import com.auth0.jwt.RegisteredClaims
|
||||
import com.auth0.jwt.algorithms.Algorithm
|
||||
import io.emeraldpay.dshackle.auth.service.RsaKeyReader
|
||||
import io.emeraldpay.dshackle.config.AuthorizationConfig
|
||||
import io.grpc.StatusException
|
||||
import org.bouncycastle.openssl.PEMParser
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
import org.junit.jupiter.api.Assertions.assertThrows
|
||||
import org.junit.jupiter.api.Assertions.assertTrue
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.springframework.util.ResourceUtils
|
||||
import java.io.StringReader
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.Paths
|
||||
import java.security.KeyFactory
|
||||
import java.security.PublicKey
|
||||
import java.security.interfaces.RSAPublicKey
|
||||
import java.security.spec.X509EncodedKeySpec
|
||||
|
||||
class AuthProcessorV1Test {
|
||||
private val processor = AuthProcessorV1(AuthorizationConfig(true, "drpc", "", ""))
|
||||
private val rsaKeyReader = RsaKeyReader()
|
||||
private val privProviderPath = ResourceUtils.getFile("classpath:keys/priv.p8.key").path
|
||||
private val publicDrpcPath = ResourceUtils.getFile("classpath:keys/public-drpc.pem").path
|
||||
private val token = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJkcnBjIiwiaWF0IjoxNjkyMTg1OTMxLCJ2ZXJzaW9uI" +
|
||||
"joiVjEifQ.BZILN0GQ7JzXGFz-GZIbFTT9E5L-miB4Nga0v4o_cQThk8gbDelBRzEfdsqxCq_ppPr3v_Own8M-vR9yQElx5nEdlI4xe5QAMdIvr3g" +
|
||||
"12fMckydX9IsW4sVQ1kJJY8RrHb-WL-uI0WSWqoMSwf-Psb-UyiEHAjc3oK7fA72lBaGT4waPHOxRBPvezwg7N934vCZvZMAftFfVgmeEtbCeD7bF" +
|
||||
"umEr0uEmkIKPTg4QwP-VMvqoLBYpMiJVzP_Ipg_wRHJ7fUN0BGEPjjMvhQ_6TWByiQUBz1kTMd0Ebf_kEuXFQeiwA-FXHJpWczzh66CbbmmWAWsi" +
|
||||
"ehKw3KPZeBj0oQ"
|
||||
private val keyPair = rsaKeyReader.getKeyPair(privProviderPath, publicDrpcPath)
|
||||
|
||||
@Test
|
||||
fun `verify tokens is successful`() {
|
||||
val publicProviderPath = ResourceUtils.getFile("classpath:keys/public.pem").path
|
||||
|
||||
val providerToken = processor.process(keyPair, token).token
|
||||
val verifier: JWTVerifier = JWT.require(Algorithm.RSA256(generatePublicKey(publicProviderPath) as RSAPublicKey, null))
|
||||
.withClaim(VERSION, "V1")
|
||||
.build()
|
||||
val decodedToken = verifier.verify(providerToken)
|
||||
assertTrue(!decodedToken.getClaim(SESSION_ID).isMissing)
|
||||
assertTrue(!decodedToken.getClaim(RegisteredClaims.ISSUED_AT).isMissing)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `verify token is failed by wrong key`() {
|
||||
val publicProviderPath = ResourceUtils.getFile("classpath:keys/wrong-public.pem").path
|
||||
val keyPair = rsaKeyReader.getKeyPair(privProviderPath, publicProviderPath)
|
||||
|
||||
val e = assertThrows(StatusException::class.java) { processor.process(keyPair, token) }
|
||||
assertEquals(
|
||||
"INVALID_ARGUMENT: Invalid token: The Token's Signature resulted invalid when verified using the Algorithm: SHA256withRSA",
|
||||
e.message
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `verify token is failed if no issuer`() {
|
||||
val invalidToken = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJkcnBjY2NjIiwiaWF0IjoxNjkyMTg3NDMwLCJ2ZXJzaW" +
|
||||
"9uIjoiVjEifQ.J1WJ1GvjNJ9JQiMK0bwvtGX1o9P93F5-921myIx3TMa2X48qIG0GVEcoMgv01ca-_aisW-Amk27ygI09dPKE__Ijr6JhZ" +
|
||||
"fDVNkw_ZArtQTcjhhJiCl3pqsouOlojc8EolpYUmyOefqemqycG0B84ibKAWTdXOtjibt1P5szWjIIV9yOYV7lTJkC0B5swcjjaMvTEPU7y" +
|
||||
"ZJhg_wvCvT67yFM1K_Wnhys3-j-Xv1Y2wOkxNt4i5LKFDtMZml5eTIEscDpjp5ARjaSTW_Rs1Eixqltx_wz1ALiS0QXOJpX7pVMJjRcth4Nu" +
|
||||
"R87ej434XoHZWqDmvOEM6M855WeHaO761A"
|
||||
|
||||
val e = assertThrows(StatusException::class.java) { processor.process(keyPair, invalidToken) }
|
||||
assertEquals(
|
||||
"INVALID_ARGUMENT: Invalid token: The Claim 'iss' value doesn't match the required issuer.",
|
||||
e.message
|
||||
)
|
||||
}
|
||||
|
||||
private fun generatePublicKey(path: String): PublicKey {
|
||||
val publicKeyReader = StringReader(Files.readString(Paths.get(path)))
|
||||
|
||||
val publicPem = PEMParser(publicKeyReader).readPemObject()
|
||||
|
||||
val publicKeySpec = X509EncodedKeySpec(publicPem.content)
|
||||
|
||||
return KeyFactory.getInstance("RSA").generatePublic(publicKeySpec)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package io.emeraldpay.dshackle.auth.processor
|
||||
|
||||
import io.emeraldpay.dshackle.auth.AuthContext
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
import org.junit.jupiter.api.Assertions.assertTrue
|
||||
import org.junit.jupiter.api.BeforeEach
|
||||
import org.junit.jupiter.api.Test
|
||||
import java.time.Instant
|
||||
import java.time.temporal.ChronoUnit
|
||||
|
||||
class TokenProcessorTest {
|
||||
private val tokenProcessor = TokenProcessor()
|
||||
|
||||
@BeforeEach
|
||||
fun removeSessions() {
|
||||
AuthContext.sessions.clear()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `invalidate all tokens`() {
|
||||
AuthContext.putTokenInContext(
|
||||
AuthContext.TokenWrapper("token", Instant.now().minus(1, ChronoUnit.HOURS), "session1")
|
||||
)
|
||||
AuthContext.putTokenInContext(
|
||||
AuthContext.TokenWrapper("token", Instant.now().minus(1, ChronoUnit.HOURS), "session2")
|
||||
)
|
||||
AuthContext.putTokenInContext(
|
||||
AuthContext.TokenWrapper("token", Instant.now().minus(1, ChronoUnit.HOURS), "session3")
|
||||
)
|
||||
|
||||
tokenProcessor.invalidateTokens()
|
||||
|
||||
assertTrue(AuthContext.sessions.isEmpty())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `tokens are still in the context after invalidation`() {
|
||||
val token1 = AuthContext.TokenWrapper("token", Instant.now().minus(30, ChronoUnit.MINUTES), "session1")
|
||||
val token2 = AuthContext.TokenWrapper("token", Instant.now().minus(30, ChronoUnit.MINUTES), "session2")
|
||||
val token3 = AuthContext.TokenWrapper("token", Instant.now().minus(30, ChronoUnit.MINUTES), "session3")
|
||||
AuthContext.putTokenInContext(token1)
|
||||
AuthContext.putTokenInContext(token2)
|
||||
AuthContext.putTokenInContext(token3)
|
||||
|
||||
tokenProcessor.invalidateTokens()
|
||||
|
||||
assertEquals(3, AuthContext.sessions.size)
|
||||
assertEquals(
|
||||
mapOf(token1.sessionId to token1, token2.sessionId to token2, token3.sessionId to token3),
|
||||
AuthContext.sessions
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
package io.emeraldpay.dshackle.auth.service
|
||||
|
||||
import io.emeraldpay.dshackle.auth.AuthContext
|
||||
import io.emeraldpay.dshackle.auth.processor.AuthProcessor
|
||||
import io.emeraldpay.dshackle.auth.processor.AuthProcessorResolver
|
||||
import io.emeraldpay.dshackle.config.AuthorizationConfig
|
||||
import io.grpc.StatusException
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
import org.junit.jupiter.api.Assertions.assertThrows
|
||||
import org.junit.jupiter.api.Assertions.assertTrue
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.mockito.Mockito.mock
|
||||
import org.mockito.Mockito.times
|
||||
import org.mockito.Mockito.verify
|
||||
import org.mockito.Mockito.`when`
|
||||
import java.security.PrivateKey
|
||||
import java.security.PublicKey
|
||||
import java.time.Instant
|
||||
import java.util.concurrent.CompletableFuture
|
||||
|
||||
class AuthServiceTest {
|
||||
private val rsaKeyReader = mock(KeyReader::class.java)
|
||||
private val mockV1Processor = mock(AuthProcessor::class.java)
|
||||
private val factory = AuthProcessorResolver(mockV1Processor)
|
||||
|
||||
private val token = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJkcnBjIiwiaWF0IjoxNjkyMTg1OTMxLCJ2ZXJzaW9uI" +
|
||||
"joiVjEifQ.BZILN0GQ7JzXGFz-GZIbFTT9E5L-miB4Nga0v4o_cQThk8gbDelBRzEfdsqxCq_ppPr3v_Own8M-vR9yQElx5nEdlI4xe5QAMdIvr3g" +
|
||||
"12fMckydX9IsW4sVQ1kJJY8RrHb-WL-uI0WSWqoMSwf-Psb-UyiEHAjc3oK7fA72lBaGT4waPHOxRBPvezwg7N934vCZvZMAftFfVgmeEtbCeD7bF" +
|
||||
"umEr0uEmkIKPTg4QwP-VMvqoLBYpMiJVzP_Ipg_wRHJ7fUN0BGEPjjMvhQ_6TWByiQUBz1kTMd0Ebf_kEuXFQeiwA-FXHJpWczzh66CbbmmWAWsi" +
|
||||
"ehKw3KPZeBj0oQ"
|
||||
|
||||
@Test
|
||||
fun `unimplemented error if auth is disabled`() {
|
||||
val authService = AuthService(AuthorizationConfig.default(), rsaKeyReader, factory)
|
||||
|
||||
val e = assertThrows(StatusException::class.java) { authService.authenticate("") }
|
||||
assertEquals("UNIMPLEMENTED: Authentication process is not enabled", e.message)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `auth is successful`() {
|
||||
val tokenWrapper = AuthContext.TokenWrapper(
|
||||
"token", Instant.now(), "sessionId"
|
||||
)
|
||||
val authService = AuthService(AuthorizationConfig(true, "drpc", "privPath", "pubPath"), rsaKeyReader, factory)
|
||||
val pair = KeyReader.Keys(mock(PrivateKey::class.java), mock(PublicKey::class.java))
|
||||
|
||||
`when`(rsaKeyReader.getKeyPair("privPath", "pubPath"))
|
||||
.thenReturn(pair)
|
||||
`when`(mockV1Processor.process(pair, token)).thenReturn(tokenWrapper)
|
||||
|
||||
authService.authenticate(token)
|
||||
verify(rsaKeyReader).getKeyPair("privPath", "pubPath")
|
||||
verify(mockV1Processor).process(pair, token)
|
||||
assertTrue(AuthContext.sessions.containsKey(tokenWrapper.sessionId))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `parallel try to auth is successful`() {
|
||||
val tokenWrapper = AuthContext.TokenWrapper(
|
||||
"token", Instant.now(), "sessionId"
|
||||
)
|
||||
val tokenWrapper1 = AuthContext.TokenWrapper(
|
||||
"token", Instant.now(), "sessionIdNext"
|
||||
)
|
||||
val pair = KeyReader.Keys(mock(PrivateKey::class.java), mock(PublicKey::class.java))
|
||||
val authService = AuthService(AuthorizationConfig(true, "drpc", "privPath", "pubPath"), rsaKeyReader, factory)
|
||||
|
||||
`when`(rsaKeyReader.getKeyPair("privPath", "pubPath")).thenReturn(pair)
|
||||
`when`(mockV1Processor.process(pair, token))
|
||||
.thenReturn(tokenWrapper)
|
||||
.thenReturn(tokenWrapper1)
|
||||
|
||||
val task = Runnable { authService.authenticate(token) }
|
||||
|
||||
CompletableFuture.allOf(
|
||||
CompletableFuture.runAsync(task), CompletableFuture.runAsync(task)
|
||||
).join()
|
||||
|
||||
verify(rsaKeyReader, times(2)).getKeyPair("privPath", "pubPath")
|
||||
verify(mockV1Processor, times(2)).process(pair, token)
|
||||
assertTrue(AuthContext.sessions.containsKey(tokenWrapper.sessionId))
|
||||
assertTrue(AuthContext.sessions.containsKey(tokenWrapper1.sessionId))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
package io.emeraldpay.dshackle.auth.service
|
||||
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
import org.junit.jupiter.api.Assertions.assertTrue
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.springframework.util.ResourceUtils
|
||||
import java.math.BigInteger
|
||||
import java.security.interfaces.RSAPrivateKey
|
||||
import java.security.interfaces.RSAPublicKey
|
||||
|
||||
class RsaKeyReaderTest {
|
||||
private val rsaKeyReader = RsaKeyReader()
|
||||
|
||||
@Test
|
||||
fun `read rsa keys`() {
|
||||
val privPath = ResourceUtils.getFile("classpath:keys/priv.p8.key").path
|
||||
val publicPath = ResourceUtils.getFile("classpath:keys/public-drpc.pem").path
|
||||
|
||||
val pair = rsaKeyReader.getKeyPair(privPath, publicPath)
|
||||
val privateKey = pair.providerPrivateKey
|
||||
val publicKey = pair.externalPublicKey
|
||||
|
||||
assertEquals("RSA", publicKey.algorithm)
|
||||
assertEquals("RSA", privateKey.algorithm)
|
||||
assertTrue(privateKey is RSAPrivateKey)
|
||||
assertTrue(publicKey is RSAPublicKey)
|
||||
assertTrue {
|
||||
(publicKey as RSAPublicKey)
|
||||
.run {
|
||||
val pubExponent = BigInteger("65537")
|
||||
val pubModulus = BigInteger(
|
||||
"240721495118071395463408682378448427874712549978328672120875120395" +
|
||||
"572530650526403138656868181996853461531503006596703750232991335166031612653183175739344513002793" +
|
||||
"13348833980409910131805866311837600488070189526246817317791758685747539443624701130779843529258916" +
|
||||
"650627570641542946773123818063304222185205897480168513227047010620610697362185901253798594085716467" +
|
||||
"92700927695491574061920135297311257486773262784524717857554194798974994838378321973611865011031457" +
|
||||
"8859834631052697211398759550437148562864384448640344034211059348047816397935347753413946137935402" +
|
||||
"562264627177210569215727831448291731093608051334025332988696191"
|
||||
)
|
||||
publicExponent == pubExponent && modulus == pubModulus
|
||||
}
|
||||
}
|
||||
assertTrue {
|
||||
(privateKey as RSAPrivateKey)
|
||||
.run {
|
||||
val privModulus = BigInteger(
|
||||
"2832492054027911929186769713106999105750517776225145627897719982164048887089826085864021820" +
|
||||
"93695282142899084731750598834879176944012613634073603363270499693159806868257221336886781437442" +
|
||||
"7025894179697109350195776864078690751332857203663624730020413376539574511375327958483292435380" +
|
||||
"2072047577218574632871804997852794886795318485439021501388394533101191305015940756335545719496" +
|
||||
"04557847518214006893286271137213491059994279470633688916032797849945887259137876037521925382454" +
|
||||
"762238089597912107394417801381736627011587603418078578130206830219321107525729614610300336784925" +
|
||||
"6741484379041499791605663418628739419306248595872949"
|
||||
)
|
||||
val privExponent = BigInteger(
|
||||
"2704777724417882789147602456408780048462378556719441898242139736695157047955437327090684858" +
|
||||
"9022442508913912020206014886681429805316382175637569992035330289450733224635356250217508515675" +
|
||||
"1746547003301653260530619509144105809847739245799589488158044325999968951997941276374874356761" +
|
||||
"7744591423894125208163912446945277960899800043458394321204754817284255735206518583285355872405" +
|
||||
"3966258052672146605639969648388744960700258048094135244952249403403074548577386549705289154261" +
|
||||
"2346391566526533514196717633725503640437930121313225102536030460301806443109607461162231878852" +
|
||||
"64869837629637060803692577216204745443259318662678133769"
|
||||
)
|
||||
|
||||
privateExponent == privExponent && modulus == privModulus
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
2
src/test/resources/configs/auth-disabled.yaml
Normal file
2
src/test/resources/configs/auth-disabled.yaml
Normal file
@@ -0,0 +1,2 @@
|
||||
auth:
|
||||
enabled: false
|
||||
6
src/test/resources/configs/auth-with-wrong-priv-key.yaml
Normal file
6
src/test/resources/configs/auth-with-wrong-priv-key.yaml
Normal file
@@ -0,0 +1,6 @@
|
||||
auth:
|
||||
enabled: true
|
||||
publicKeyOwner: drpc
|
||||
keys:
|
||||
provider-private-key: "classpath:keys/priv-wrong.p8.key"
|
||||
external-public-key: "classpath:keys/pub-wrong.key"
|
||||
6
src/test/resources/configs/auth-with-wrong-pub-key.yaml
Normal file
6
src/test/resources/configs/auth-with-wrong-pub-key.yaml
Normal file
@@ -0,0 +1,6 @@
|
||||
auth:
|
||||
enabled: true
|
||||
publicKeyOwner: drpc
|
||||
keys:
|
||||
provider-private-key: "classpath:keys/priv.p8.key"
|
||||
external-public-key: "classpath:keys/pub-wrong.key"
|
||||
2
src/test/resources/configs/auth-without-key-owner.yaml
Normal file
2
src/test/resources/configs/auth-without-key-owner.yaml
Normal file
@@ -0,0 +1,2 @@
|
||||
auth:
|
||||
enabled: true
|
||||
3
src/test/resources/configs/auth-without-key-pair.yaml
Normal file
3
src/test/resources/configs/auth-without-key-pair.yaml
Normal file
@@ -0,0 +1,3 @@
|
||||
auth:
|
||||
enabled: true
|
||||
publicKeyOwner: drpc
|
||||
5
src/test/resources/configs/auth-without-private-key.yaml
Normal file
5
src/test/resources/configs/auth-without-private-key.yaml
Normal file
@@ -0,0 +1,5 @@
|
||||
auth:
|
||||
enabled: true
|
||||
publicKeyOwner: drpc
|
||||
keys:
|
||||
external-public-key: /keys/pub.key
|
||||
5
src/test/resources/configs/auth-without-public-key.yaml
Normal file
5
src/test/resources/configs/auth-without-public-key.yaml
Normal file
@@ -0,0 +1,5 @@
|
||||
auth:
|
||||
enabled: true
|
||||
publicKeyOwner: drpc
|
||||
keys:
|
||||
provider-private-key: /keys/priv.p8.key
|
||||
@@ -11,6 +11,13 @@ tls:
|
||||
require: false
|
||||
ca: "/path/ca.dshackle.test.crt"
|
||||
|
||||
auth:
|
||||
enabled: true
|
||||
publicKeyOwner: drpc
|
||||
keys:
|
||||
provider-private-key: "classpath:keys/priv.p8.key"
|
||||
external-public-key: "classpath:keys/public.pem"
|
||||
|
||||
cache:
|
||||
redis:
|
||||
enabled: true
|
||||
|
||||
28
src/test/resources/keys/priv-drpc.p8.key
Normal file
28
src/test/resources/keys/priv-drpc.p8.key
Normal file
@@ -0,0 +1,28 @@
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQC+sCrzY7pN4L3C
|
||||
uPyV8Q5dFilQYuP8kAWF2X6nAz2WFzHAPQJQXkwUeFz0T1Py0TtwrX6RcYu1KOuC
|
||||
Bj78KvtptThFxFk9T9I3mpi00ZoIGVGPniW/4lMdN2DYqsXF/2r7K6dmtA9JQKAl
|
||||
RjQiIkclwIn2/JYIFWi5viaeBl1XLG7m06TdksGt5JJ/c6yk0cFn0jlFeVLZr087
|
||||
EWkj+3NN5+7dNHc+LCGunKHXX+cl+GHGc3JO6a953t6POhHXZh+/lX+WzD83uKUN
|
||||
bYdCn0eyz2++6/lOhYR7x6Nc2d+ikEGzqGDnalD3bJPLV0hm5fXTUyd2zipPDtXc
|
||||
R3UQsdZ/AgMBAAECggEBAJmDyEBDxGQYD40pUigAdhjY04/k3TiiVd/pNj6MrFMd
|
||||
/y6Nr0Q2djdwox0IlEQC0ECpXCEJcXj6JYd7Mc/fZqI2g8QfC90BWvLq4g+IQuJM
|
||||
R85AepGdSl5WDADCkxAQzfbBsTZMtZW6knMPrr7gK7kn5apStRJy8em/POWxqAmZ
|
||||
1WLZP/CC2EBNk8zI+vJkZpWh4bbAXYalPBr+e+QXMdBJMlm8sO/cexqQSG3A2Igj
|
||||
4DO16wxQn7ZahUF/caM4Y+v4LyO8fF75h+CLW8dZ5f344hI8QoEQWzbRjeDlgmie
|
||||
BMkOAiB2Q6ejLO+3iPptVyTdFGrz5PkVfiHbVaD4A3kCgYEA9JwKDSBA03EyeIuf
|
||||
PFkbcdiAOr8mSrtnfaFjfICOGYmiC/FQjVVa4l984J0OLRAIr+aEHe4zKY2wcWhZ
|
||||
EaJGBaBAD7yEUR5nAMP+8zVnUNgVjIvvQuDhiVzPSl7qFj4QZOpztv2zBKmneBsW
|
||||
HE2M5fxBcNxewSuPVPGzEh/p9/sCgYEAx5FWnAPu6SbCpXyK9JNty/Y9d+Ls+g/R
|
||||
U9zZPAeuIFDclDEteP/GfE8fRkQ50IRRoxP0DUUDnNgHkqnCk+L/yB7lg4N9LlaK
|
||||
9IF3o/LLuj40LwWZa8/E+72+16XtjuBCVNcglv9Ph/maGL0sYLXOYJUYMvslJm/4
|
||||
ITtZicoiwE0CgYAwN/5HPh9pTvwrBSL7q3kchRgp/HpY6v5opoLNDS513ErEXeqK
|
||||
IdRLoZUlVfBwc6OywRc0KzuMqnCounAsaLey7jcSow/WSc72OKyuVs2qAx6kWQVu
|
||||
QpRTFqeKOpGcltl2ez0aSoKanbL0mypNo//taj+gNuC65ZJYpViubvoZNwKBgQC7
|
||||
Eti3Y1B/ql/oNPklD1ZrTaaNTWGLf2xgSrQwe5qUnHhJSlgwBsQPHzRX7/iqKH9G
|
||||
eZvbHIiobDGdIBlP/Ah9lcjcIVQlLecQEJUQULb9HPZZ5cvNrnQe792H1Pj9Z+eG
|
||||
FCtuoGHzOt1it2J0Pbj5/Ik0sQxAHdHhhPsIGT2+OQKBgQDWcgBLTd1VNVR0iX74
|
||||
aJx5VhYCMHQ+bA8qI46tyDid/c5GibXuWf8kHUfqlfcsRzv+HGsGRvyyvYn5LimE
|
||||
HB9U3xy0z5q5i+G/6iwftw+ObaVgVYr5D5ovl4DT56nlG8IOYVNA1T1Heewkk5jc
|
||||
ZrLvcE6YXcAKE1y5RWaDprVmjw==
|
||||
-----END PRIVATE KEY-----
|
||||
28
src/test/resources/keys/priv.p8.key
Normal file
28
src/test/resources/keys/priv.p8.key
Normal file
@@ -0,0 +1,28 @@
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDgYGhG/i+93a7W
|
||||
oLK7T4SQkPH1+q1UppWxlPvQ3FunlOx8iGkTMKYx+iJEUm3E36eX466gVJYJpGPp
|
||||
n+oYQ0f2QDx4zcElcqTvJhu644BBp2b1qim4f+iaWdEWUmdgIEfoePHe9NkUyIxK
|
||||
m/TOYwtLSa8Rb+Vs7XePDUi9F/t/aQrHe6AwII8vKxgdvHUsqL+8A9oHIAMFZ6gL
|
||||
4tasbFJqCfGaMyphW2AOnVNnkDOvLAGi2C9q9cjWnHZRhaHO34pEFnP6Wpor7rau
|
||||
xSqS3WYtM1/Uxff/l5JCMB9AyRofbZSNWpsZYUesubSghzRfxcZzzk76lXPeDKS+
|
||||
mb8P1Jy1AgMBAAECggEBANZCeZFBbWmLZUfC0KoZdudt4gAYTqhFIzEeqZB6vHM8
|
||||
vM72nBAdJrcWut4vMG8Ne8aJPtoPq+6tMlOHx01AAlneYWCiakxOO7260EQYtwL6
|
||||
zlDNG2X5jq7GEemU6u5aJRCTXbSgbyNvlrgwUQc8gS51wF2QUq8/3CmYb9tSKXEx
|
||||
nRSvIT4RzMHxPfHgkSppSIJt4AQcof25f7ImVFNdv3rSlKCKhPTjMf87gBzOqlxg
|
||||
xyAfIuxqpaDeY9rjywVahhthDDfRk7zt7m52huSIxg1oQuRKHY8iExCYXZgqc5Il
|
||||
oelZN9ZleA+y+2LjSdpiU6yp63IUXo52246knPo7PAkCgYEA/S1E0ZDrc51trjcj
|
||||
3KbHbq+O0w81WCjUzSWwtykVh1cyhZbwasqFUq2ydeYvpyq1z0sxbkUCjB9TV3G7
|
||||
LvQg+cUJmUgAvJEuP9BwCb2lwCfBnqVK33T1QnbG/AynPoey5SiyZ7/AnRcujGLo
|
||||
OwsttnxPw7NM1Us27bn+bF9bSgMCgYEA4uDshYJbJa6xgqkvdlZvZZnmxxjq0HP8
|
||||
htR3hdh2/cMsDRr/gjUknQPsTLZKVxTQw+qJaYVBZpskWd0y5Bx5e+mhitHGr7z9
|
||||
5FLz2wrWuQBFw0Lh97zcPhAiupAlkrpW2VFzc3wQvVjiJv9oaxFX3zXnFXXFN668
|
||||
Px2K423JnOcCgYBP3LTFqoIrITDU8L3WP6HPT3+tIjIAkji2UbpRvJr261GbhEZo
|
||||
WWP+9Z3CxQ6pG7boId8A2rH4A3WlstGJ+SZhw37IpEbfNKizJowA9prPZ3sTES7y
|
||||
GBG5moAgR9mFxwZudQz53yniI8riK9z3qwoLc2Ex6WBGNAiqqq4P/3BrgwKBgEX2
|
||||
urmJM3ZxYsg6SCqkCQI57ZLkOjVCL1Oc3abm0/r5XvJDqxuKK9pHxWz6of4sqxxf
|
||||
jTR/JWXw3crgjbsOlOADcg+PFUhIbbslYZHgy8qNLPZD/88X7IsvCqahoRSYZgPq
|
||||
PEIWtkrNyB/ij17FIGXzB2n0wXakaeTExXnQ92VtAoGAcwA5881UZWvXEdz7hoOt
|
||||
BPssHkyKEPKWVhyyuyhJ4M2c1//vCdQM53RiWuNo5aU3kztS5+o2bQYm9VkmRL0o
|
||||
yjR+UP61ekyK8vd+c0hG5m++Rp6SqjLSj3b4Ln7jtarM9OdXD12l1qZetBrDXnFi
|
||||
bd1Cq+3lNGpl9Ut/u5DbWgw=
|
||||
-----END PRIVATE KEY-----
|
||||
9
src/test/resources/keys/public-drpc.pem
Normal file
9
src/test/resources/keys/public-drpc.pem
Normal file
@@ -0,0 +1,9 @@
|
||||
-----BEGIN PUBLIC KEY-----
|
||||
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvrAq82O6TeC9wrj8lfEO
|
||||
XRYpUGLj/JAFhdl+pwM9lhcxwD0CUF5MFHhc9E9T8tE7cK1+kXGLtSjrggY+/Cr7
|
||||
abU4RcRZPU/SN5qYtNGaCBlRj54lv+JTHTdg2KrFxf9q+yunZrQPSUCgJUY0IiJH
|
||||
JcCJ9vyWCBVoub4mngZdVyxu5tOk3ZLBreSSf3OspNHBZ9I5RXlS2a9POxFpI/tz
|
||||
Tefu3TR3Piwhrpyh11/nJfhhxnNyTumved7ejzoR12Yfv5V/lsw/N7ilDW2HQp9H
|
||||
ss9vvuv5ToWEe8ejXNnfopBBs6hg52pQ92yTy1dIZuX101Mnds4qTw7V3Ed1ELHW
|
||||
fwIDAQAB
|
||||
-----END PUBLIC KEY-----
|
||||
9
src/test/resources/keys/public.pem
Normal file
9
src/test/resources/keys/public.pem
Normal file
@@ -0,0 +1,9 @@
|
||||
-----BEGIN PUBLIC KEY-----
|
||||
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4GBoRv4vvd2u1qCyu0+E
|
||||
kJDx9fqtVKaVsZT70Nxbp5TsfIhpEzCmMfoiRFJtxN+nl+OuoFSWCaRj6Z/qGENH
|
||||
9kA8eM3BJXKk7yYbuuOAQadm9aopuH/omlnRFlJnYCBH6Hjx3vTZFMiMSpv0zmML
|
||||
S0mvEW/lbO13jw1IvRf7f2kKx3ugMCCPLysYHbx1LKi/vAPaByADBWeoC+LWrGxS
|
||||
agnxmjMqYVtgDp1TZ5AzrywBotgvavXI1px2UYWhzt+KRBZz+lqaK+62rsUqkt1m
|
||||
LTNf1MX3/5eSQjAfQMkaH22UjVqbGWFHrLm0oIc0X8XGc85O+pVz3gykvpm/D9Sc
|
||||
tQIDAQAB
|
||||
-----END PUBLIC KEY-----
|
||||
9
src/test/resources/keys/wrong-public.pem
Normal file
9
src/test/resources/keys/wrong-public.pem
Normal file
@@ -0,0 +1,9 @@
|
||||
-----BEGIN PUBLIC KEY-----
|
||||
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAy93QvZ7evi8V9crEQzPf
|
||||
NxTdIx0tvvwUcp85TsoNfHJLAD41yjJaVPwXk2mD6KuoV7hkZjoGu5ow5pPKZUKp
|
||||
MzOXsfVIAvGf0xEowWaANZflrX87ZwsWLK+4mZHSCA92THQoGncBu3kUGFPjukVn
|
||||
vlKUcX+4lizXGDztMD35KrPXBNXziOMSxxqnL7jlWeNYDxqmCmquwy+kqF/mCwr1
|
||||
n+k6GxF1H7plSGHNZalLRB6jJUQiC6EYDYl1hPv3EdPRPwjKXQ12J6LZ+XVdOLYC
|
||||
s8Q1I1ajPsT6/rg5MjXn7paWc9GrBVG57lC7R0n9zSfo64ol3DuVQognOcU3MQT9
|
||||
rQIDAQAB
|
||||
-----END PUBLIC KEY-----
|
||||
Reference in New Issue
Block a user