From 631acc534e1df5e7472eef8ea4dc00b46acd6446 Mon Sep 17 00:00:00 2001 From: usbharu <64310155+usbharu@users.noreply.github.com> Date: Tue, 28 Nov 2023 13:34:21 +0900 Subject: [PATCH 1/6] =?UTF-8?q?feat:=20http-signature=E3=81=AESpring=20Sec?= =?UTF-8?q?urity=E3=82=92=E4=BD=BF=E7=94=A8=E3=81=97=E3=81=9F=E8=AA=8D?= =?UTF-8?q?=E8=A8=BC=E3=82=92post=E3=81=AE=E3=81=BF=E3=81=AB=E5=A4=89?= =?UTF-8?q?=E6=9B=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dev/usbharu/hideout/application/config/SecurityConfig.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/kotlin/dev/usbharu/hideout/application/config/SecurityConfig.kt b/src/main/kotlin/dev/usbharu/hideout/application/config/SecurityConfig.kt index 089a545a..dece6e79 100644 --- a/src/main/kotlin/dev/usbharu/hideout/application/config/SecurityConfig.kt +++ b/src/main/kotlin/dev/usbharu/hideout/application/config/SecurityConfig.kt @@ -81,7 +81,7 @@ class SecurityConfig { ): SecurityFilterChain { val builder = MvcRequestMatcher.Builder(introspector) http - .securityMatcher("/inbox", "/outbox", "/users/*/inbox", "/users/*/outbox", "/users/*/posts/*") + .securityMatcher("/users/*/posts/*") .addFilter(httpSignatureFilter) .addFilterBefore( ExceptionTranslationFilter(HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED)), From cc046393d6b62d4b6a4c06879e44bd5d4f941687 Mon Sep 17 00:00:00 2001 From: usbharu <64310155+usbharu@users.noreply.github.com> Date: Tue, 28 Nov 2023 13:56:39 +0900 Subject: [PATCH 2/6] =?UTF-8?q?feat:=20Signature=E3=83=98=E3=83=83?= =?UTF-8?q?=E3=83=80=E3=83=BC=E3=81=8C=E3=81=AA=E3=81=84=E5=A0=B4=E5=90=88?= =?UTF-8?q?=E3=82=B3=E3=83=B3=E3=83=88=E3=83=AD=E3=83=BC=E3=83=A9=E3=83=BC?= =?UTF-8?q?=E3=81=AE=E6=99=82=E7=82=B9=E3=81=A7401=E3=82=92=E8=BF=94?= =?UTF-8?q?=E3=81=99=E3=82=88=E3=81=86=E3=81=AB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/intTest/kotlin/activitypub/inbox/InboxTest.kt | 2 ++ .../interfaces/api/inbox/InboxControllerImpl.kt | 15 ++++++++++++--- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/src/intTest/kotlin/activitypub/inbox/InboxTest.kt b/src/intTest/kotlin/activitypub/inbox/InboxTest.kt index 92fe3aa2..4626772c 100644 --- a/src/intTest/kotlin/activitypub/inbox/InboxTest.kt +++ b/src/intTest/kotlin/activitypub/inbox/InboxTest.kt @@ -45,6 +45,7 @@ class InboxTest { content = "{}" contentType = MediaType.APPLICATION_JSON } + .asyncDispatch() .andExpect { status { isUnauthorized() } } } @@ -68,6 +69,7 @@ class InboxTest { content = "{}" contentType = MediaType.APPLICATION_JSON } + .asyncDispatch() .andExpect { status { isUnauthorized() } } } diff --git a/src/main/kotlin/dev/usbharu/hideout/activitypub/interfaces/api/inbox/InboxControllerImpl.kt b/src/main/kotlin/dev/usbharu/hideout/activitypub/interfaces/api/inbox/InboxControllerImpl.kt index 1ad9062c..6c47a5b1 100644 --- a/src/main/kotlin/dev/usbharu/hideout/activitypub/interfaces/api/inbox/InboxControllerImpl.kt +++ b/src/main/kotlin/dev/usbharu/hideout/activitypub/interfaces/api/inbox/InboxControllerImpl.kt @@ -5,6 +5,7 @@ import dev.usbharu.httpsignature.common.HttpHeaders import dev.usbharu.httpsignature.common.HttpMethod import dev.usbharu.httpsignature.common.HttpRequest import org.slf4j.LoggerFactory +import org.springframework.http.HttpHeaders.WWW_AUTHENTICATE import org.springframework.http.HttpStatus import org.springframework.http.ResponseEntity import org.springframework.web.bind.annotation.RequestBody @@ -21,6 +22,16 @@ class InboxControllerImpl(private val apService: APService) : InboxController { ): ResponseEntity { val request = (requireNotNull(RequestContextHolder.getRequestAttributes()) as ServletRequestAttributes).request + val headersList = request.headerNames?.toList().orEmpty() + if (headersList.contains("Signature").not()) { + return ResponseEntity.status(HttpStatus.UNAUTHORIZED) + .header( + WWW_AUTHENTICATE, + "Signature realm=\"Example\",headers=\"(request-target) date host digest\"" + ) + .build() + } + val parseActivity = try { apService.parseActivity(string) } catch (e: Exception) { @@ -31,7 +42,7 @@ class InboxControllerImpl(private val apService: APService) : InboxController { try { val url = request.requestURL.toString() - val headersList = request.headerNames?.toList().orEmpty() + val headers = headersList.associateWith { header -> request.getHeaders(header)?.toList().orEmpty() } @@ -43,8 +54,6 @@ class InboxControllerImpl(private val apService: APService) : InboxController { } } - println(headers) - apService.processActivity( string, parseActivity, From f0366ec5bae50ebd610d7d31009feb314d76c7c9 Mon Sep 17 00:00:00 2001 From: usbharu <64310155+usbharu@users.noreply.github.com> Date: Tue, 28 Nov 2023 14:18:51 +0900 Subject: [PATCH 3/6] =?UTF-8?q?feat:=20HTTP=20Signature=E3=81=A7=E5=BF=85?= =?UTF-8?q?=E8=A6=81=E3=81=AA=E3=83=98=E3=83=83=E3=83=80=E3=83=BC=E3=81=8C?= =?UTF-8?q?=E5=90=AB=E3=81=BE=E3=82=8C=E3=81=A6=E3=81=84=E3=82=8B=E3=81=8B?= =?UTF-8?q?=E3=82=92=E6=A4=9C=E8=A8=BC=E3=81=99=E3=82=8B=E3=82=88=E3=81=86?= =?UTF-8?q?=E3=81=AB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/inbox/InboxJobProcessor.kt | 12 +++++++++ .../application/config/SecurityConfig.kt | 2 +- .../httpsignature/HttpSignatureFilter.kt | 19 +------------ .../HttpSignatureUserDetailsService.kt | 27 ++++++++++++++++--- 4 files changed, 38 insertions(+), 22 deletions(-) diff --git a/src/main/kotlin/dev/usbharu/hideout/activitypub/service/inbox/InboxJobProcessor.kt b/src/main/kotlin/dev/usbharu/hideout/activitypub/service/inbox/InboxJobProcessor.kt index 1e4aeb2d..d1bebb2b 100644 --- a/src/main/kotlin/dev/usbharu/hideout/activitypub/service/inbox/InboxJobProcessor.kt +++ b/src/main/kotlin/dev/usbharu/hideout/activitypub/service/inbox/InboxJobProcessor.kt @@ -14,6 +14,7 @@ import dev.usbharu.hideout.core.query.UserQueryService import dev.usbharu.hideout.core.service.job.JobProcessor import dev.usbharu.hideout.util.RsaUtil import dev.usbharu.httpsignature.common.HttpHeaders +import dev.usbharu.httpsignature.common.HttpMethod import dev.usbharu.httpsignature.common.HttpRequest import dev.usbharu.httpsignature.common.PublicKey import dev.usbharu.httpsignature.verify.HttpSignatureVerifier @@ -34,6 +35,15 @@ class InboxJobProcessor( ) : JobProcessor { private suspend fun verifyHttpSignature(httpRequest: HttpRequest, signature: Signature): Boolean { + + val requiredHeaders = when (httpRequest.method) { + HttpMethod.GET -> getRequiredHeaders + HttpMethod.POST -> postRequiredHeaders + } + if (signature.headers.containsAll(requiredHeaders).not()) { + return false + } + val user = try { userQueryService.findByKeyId(signature.keyId) } catch (_: FailedToGetResourcesException) { @@ -96,5 +106,7 @@ class InboxJobProcessor( companion object { private val logger = LoggerFactory.getLogger(InboxJobProcessor::class.java) + private val postRequiredHeaders = listOf("(request-target)", "date", "host", "digest") + private val getRequiredHeaders = listOf("(request-target)", "date", "host") } } diff --git a/src/main/kotlin/dev/usbharu/hideout/application/config/SecurityConfig.kt b/src/main/kotlin/dev/usbharu/hideout/application/config/SecurityConfig.kt index dece6e79..84df4aa3 100644 --- a/src/main/kotlin/dev/usbharu/hideout/application/config/SecurityConfig.kt +++ b/src/main/kotlin/dev/usbharu/hideout/application/config/SecurityConfig.kt @@ -121,7 +121,7 @@ class SecurityConfig { userQueryService: UserQueryService ): HttpSignatureFilter { val httpSignatureFilter = - HttpSignatureFilter(DefaultSignatureHeaderParser(), transaction, apUserService, userQueryService) + HttpSignatureFilter(DefaultSignatureHeaderParser()) httpSignatureFilter.setAuthenticationManager(authenticationManager) httpSignatureFilter.setContinueFilterChainOnUnsuccessfulAuthentication(false) val authenticationEntryPointFailureHandler = diff --git a/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/springframework/httpsignature/HttpSignatureFilter.kt b/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/springframework/httpsignature/HttpSignatureFilter.kt index e814e568..d4332651 100644 --- a/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/springframework/httpsignature/HttpSignatureFilter.kt +++ b/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/springframework/httpsignature/HttpSignatureFilter.kt @@ -1,23 +1,15 @@ package dev.usbharu.hideout.core.infrastructure.springframework.httpsignature -import dev.usbharu.hideout.activitypub.service.objects.user.APUserService -import dev.usbharu.hideout.application.external.Transaction -import dev.usbharu.hideout.core.domain.exception.FailedToGetResourcesException -import dev.usbharu.hideout.core.query.UserQueryService import dev.usbharu.httpsignature.common.HttpHeaders import dev.usbharu.httpsignature.common.HttpMethod import dev.usbharu.httpsignature.common.HttpRequest import dev.usbharu.httpsignature.verify.SignatureHeaderParser import jakarta.servlet.http.HttpServletRequest -import kotlinx.coroutines.runBlocking import org.springframework.security.web.authentication.preauth.AbstractPreAuthenticatedProcessingFilter import java.net.URL class HttpSignatureFilter( - private val httpSignatureHeaderParser: SignatureHeaderParser, - private val transaction: Transaction, - private val apUserService: APUserService, - private val userQueryService: UserQueryService + private val httpSignatureHeaderParser: SignatureHeaderParser ) : AbstractPreAuthenticatedProcessingFilter() { override fun getPreAuthenticatedPrincipal(request: HttpServletRequest?): Any? { @@ -33,15 +25,6 @@ class HttpSignatureFilter( } catch (_: RuntimeException) { return "" } - runBlocking { - transaction.transaction { - try { - userQueryService.findByKeyId(signature.keyId) - } catch (_: FailedToGetResourcesException) { - apUserService.fetchPerson(signature.keyId) - } - } - } return signature.keyId } diff --git a/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/springframework/httpsignature/HttpSignatureUserDetailsService.kt b/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/springframework/httpsignature/HttpSignatureUserDetailsService.kt index a2e2a258..3acc12f6 100644 --- a/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/springframework/httpsignature/HttpSignatureUserDetailsService.kt +++ b/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/springframework/httpsignature/HttpSignatureUserDetailsService.kt @@ -5,10 +5,12 @@ import dev.usbharu.hideout.core.domain.exception.FailedToGetResourcesException import dev.usbharu.hideout.core.domain.exception.HttpSignatureVerifyException import dev.usbharu.hideout.core.query.UserQueryService import dev.usbharu.hideout.util.RsaUtil +import dev.usbharu.httpsignature.common.HttpMethod import dev.usbharu.httpsignature.common.HttpRequest import dev.usbharu.httpsignature.common.PublicKey import dev.usbharu.httpsignature.verify.FailedVerification import dev.usbharu.httpsignature.verify.HttpSignatureVerifier +import dev.usbharu.httpsignature.verify.SignatureHeaderParser import kotlinx.coroutines.runBlocking import org.slf4j.LoggerFactory import org.springframework.security.authentication.BadCredentialsException @@ -20,14 +22,16 @@ import org.springframework.security.web.authentication.preauth.PreAuthenticatedA class HttpSignatureUserDetailsService( private val userQueryService: UserQueryService, private val httpSignatureVerifier: HttpSignatureVerifier, - private val transaction: Transaction + private val transaction: Transaction, + private val httpSignatureHeaderParser: SignatureHeaderParser ) : AuthenticationUserDetailsService { override fun loadUserDetails(token: PreAuthenticatedAuthenticationToken): UserDetails = runBlocking { if (token.principal !is String) { throw IllegalStateException("Token is not String") } - if (token.credentials !is HttpRequest) { + val credentials = token.credentials + if (credentials !is HttpRequest) { throw IllegalStateException("Credentials is not HttpRequest") } @@ -40,10 +44,25 @@ class HttpSignatureUserDetailsService( } } + val signature = httpSignatureHeaderParser.parse(credentials.headers) + + val requiredHeaders = when (credentials.method) { + HttpMethod.GET -> getRequiredHeaders + HttpMethod.POST -> postRequiredHeaders + } + if (signature.headers.containsAll(requiredHeaders).not()) { + logger.warn( + "FAILED Verify HTTP Signature. required headers: {} but actual: {}", + requiredHeaders, + signature.headers + ) + throw BadCredentialsException("HTTP Signature. required headers: $requiredHeaders") + } + @Suppress("TooGenericExceptionCaught") val verify = try { httpSignatureVerifier.verify( - token.credentials as HttpRequest, + credentials, PublicKey(RsaUtil.decodeRsaPublicKeyPem(findByKeyId.publicKey), keyId) ) } catch (e: RuntimeException) { @@ -67,5 +86,7 @@ class HttpSignatureUserDetailsService( companion object { private val logger = LoggerFactory.getLogger(HttpSignatureUserDetailsService::class.java) + private val postRequiredHeaders = listOf("(request-target)", "date", "host", "digest") + private val getRequiredHeaders = listOf("(request-target)", "date", "host") } } From b1356e849694514006896313563a3a2e6fb1c554 Mon Sep 17 00:00:00 2001 From: usbharu <64310155+usbharu@users.noreply.github.com> Date: Tue, 28 Nov 2023 14:18:51 +0900 Subject: [PATCH 4/6] =?UTF-8?q?test:=20=E3=83=86=E3=82=B9=E3=83=88?= =?UTF-8?q?=E3=82=92=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../kotlin/activitypub/inbox/InboxTest.kt | 2 ++ .../api/inbox/InboxControllerImpl.kt | 1 - .../service/inbox/InboxJobProcessor.kt | 11 ++++++++ .../application/config/SecurityConfig.kt | 10 ++++--- .../httpsignature/HttpSignatureFilter.kt | 19 +------------ .../HttpSignatureUserDetailsService.kt | 27 ++++++++++++++++--- .../api/inbox/InboxControllerImplTest.kt | 6 +++++ 7 files changed, 50 insertions(+), 26 deletions(-) diff --git a/src/intTest/kotlin/activitypub/inbox/InboxTest.kt b/src/intTest/kotlin/activitypub/inbox/InboxTest.kt index 4626772c..080639b4 100644 --- a/src/intTest/kotlin/activitypub/inbox/InboxTest.kt +++ b/src/intTest/kotlin/activitypub/inbox/InboxTest.kt @@ -56,6 +56,7 @@ class InboxTest { .post("/inbox") { content = "{}" contentType = MediaType.APPLICATION_JSON + header("Signature", "") } .asyncDispatch() .andExpect { status { isAccepted() } } @@ -80,6 +81,7 @@ class InboxTest { .post("/users/hoge/inbox") { content = "{}" contentType = MediaType.APPLICATION_JSON + header("Signature", "") } .asyncDispatch() .andExpect { status { isAccepted() } } diff --git a/src/main/kotlin/dev/usbharu/hideout/activitypub/interfaces/api/inbox/InboxControllerImpl.kt b/src/main/kotlin/dev/usbharu/hideout/activitypub/interfaces/api/inbox/InboxControllerImpl.kt index 6c47a5b1..d87d045a 100644 --- a/src/main/kotlin/dev/usbharu/hideout/activitypub/interfaces/api/inbox/InboxControllerImpl.kt +++ b/src/main/kotlin/dev/usbharu/hideout/activitypub/interfaces/api/inbox/InboxControllerImpl.kt @@ -42,7 +42,6 @@ class InboxControllerImpl(private val apService: APService) : InboxController { try { val url = request.requestURL.toString() - val headers = headersList.associateWith { header -> request.getHeaders(header)?.toList().orEmpty() } diff --git a/src/main/kotlin/dev/usbharu/hideout/activitypub/service/inbox/InboxJobProcessor.kt b/src/main/kotlin/dev/usbharu/hideout/activitypub/service/inbox/InboxJobProcessor.kt index 1e4aeb2d..1ca31144 100644 --- a/src/main/kotlin/dev/usbharu/hideout/activitypub/service/inbox/InboxJobProcessor.kt +++ b/src/main/kotlin/dev/usbharu/hideout/activitypub/service/inbox/InboxJobProcessor.kt @@ -14,6 +14,7 @@ import dev.usbharu.hideout.core.query.UserQueryService import dev.usbharu.hideout.core.service.job.JobProcessor import dev.usbharu.hideout.util.RsaUtil import dev.usbharu.httpsignature.common.HttpHeaders +import dev.usbharu.httpsignature.common.HttpMethod import dev.usbharu.httpsignature.common.HttpRequest import dev.usbharu.httpsignature.common.PublicKey import dev.usbharu.httpsignature.verify.HttpSignatureVerifier @@ -34,6 +35,14 @@ class InboxJobProcessor( ) : JobProcessor { private suspend fun verifyHttpSignature(httpRequest: HttpRequest, signature: Signature): Boolean { + val requiredHeaders = when (httpRequest.method) { + HttpMethod.GET -> getRequiredHeaders + HttpMethod.POST -> postRequiredHeaders + } + if (signature.headers.containsAll(requiredHeaders).not()) { + return false + } + val user = try { userQueryService.findByKeyId(signature.keyId) } catch (_: FailedToGetResourcesException) { @@ -96,5 +105,7 @@ class InboxJobProcessor( companion object { private val logger = LoggerFactory.getLogger(InboxJobProcessor::class.java) + private val postRequiredHeaders = listOf("(request-target)", "date", "host", "digest") + private val getRequiredHeaders = listOf("(request-target)", "date", "host") } } diff --git a/src/main/kotlin/dev/usbharu/hideout/application/config/SecurityConfig.kt b/src/main/kotlin/dev/usbharu/hideout/application/config/SecurityConfig.kt index dece6e79..499e68dd 100644 --- a/src/main/kotlin/dev/usbharu/hideout/application/config/SecurityConfig.kt +++ b/src/main/kotlin/dev/usbharu/hideout/application/config/SecurityConfig.kt @@ -121,7 +121,7 @@ class SecurityConfig { userQueryService: UserQueryService ): HttpSignatureFilter { val httpSignatureFilter = - HttpSignatureFilter(DefaultSignatureHeaderParser(), transaction, apUserService, userQueryService) + HttpSignatureFilter(DefaultSignatureHeaderParser()) httpSignatureFilter.setAuthenticationManager(authenticationManager) httpSignatureFilter.setContinueFilterChainOnUnsuccessfulAuthentication(false) val authenticationEntryPointFailureHandler = @@ -134,18 +134,20 @@ class SecurityConfig { @Bean fun httpSignatureAuthenticationProvider(transaction: Transaction): PreAuthenticatedAuthenticationProvider { val provider = PreAuthenticatedAuthenticationProvider() + val signatureHeaderParser = DefaultSignatureHeaderParser() provider.setPreAuthenticatedUserDetailsService( HttpSignatureUserDetailsService( userQueryService, HttpSignatureVerifierComposite( mapOf( "rsa-sha256" to RsaSha256HttpSignatureVerifier( - DefaultSignatureHeaderParser(), RsaSha256HttpSignatureSigner() + signatureHeaderParser, RsaSha256HttpSignatureSigner() ) ), - DefaultSignatureHeaderParser() + signatureHeaderParser ), - transaction + transaction, + signatureHeaderParser ) ) provider.setUserDetailsChecker(AccountStatusUserDetailsChecker()) diff --git a/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/springframework/httpsignature/HttpSignatureFilter.kt b/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/springframework/httpsignature/HttpSignatureFilter.kt index e814e568..d4332651 100644 --- a/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/springframework/httpsignature/HttpSignatureFilter.kt +++ b/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/springframework/httpsignature/HttpSignatureFilter.kt @@ -1,23 +1,15 @@ package dev.usbharu.hideout.core.infrastructure.springframework.httpsignature -import dev.usbharu.hideout.activitypub.service.objects.user.APUserService -import dev.usbharu.hideout.application.external.Transaction -import dev.usbharu.hideout.core.domain.exception.FailedToGetResourcesException -import dev.usbharu.hideout.core.query.UserQueryService import dev.usbharu.httpsignature.common.HttpHeaders import dev.usbharu.httpsignature.common.HttpMethod import dev.usbharu.httpsignature.common.HttpRequest import dev.usbharu.httpsignature.verify.SignatureHeaderParser import jakarta.servlet.http.HttpServletRequest -import kotlinx.coroutines.runBlocking import org.springframework.security.web.authentication.preauth.AbstractPreAuthenticatedProcessingFilter import java.net.URL class HttpSignatureFilter( - private val httpSignatureHeaderParser: SignatureHeaderParser, - private val transaction: Transaction, - private val apUserService: APUserService, - private val userQueryService: UserQueryService + private val httpSignatureHeaderParser: SignatureHeaderParser ) : AbstractPreAuthenticatedProcessingFilter() { override fun getPreAuthenticatedPrincipal(request: HttpServletRequest?): Any? { @@ -33,15 +25,6 @@ class HttpSignatureFilter( } catch (_: RuntimeException) { return "" } - runBlocking { - transaction.transaction { - try { - userQueryService.findByKeyId(signature.keyId) - } catch (_: FailedToGetResourcesException) { - apUserService.fetchPerson(signature.keyId) - } - } - } return signature.keyId } diff --git a/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/springframework/httpsignature/HttpSignatureUserDetailsService.kt b/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/springframework/httpsignature/HttpSignatureUserDetailsService.kt index a2e2a258..3acc12f6 100644 --- a/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/springframework/httpsignature/HttpSignatureUserDetailsService.kt +++ b/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/springframework/httpsignature/HttpSignatureUserDetailsService.kt @@ -5,10 +5,12 @@ import dev.usbharu.hideout.core.domain.exception.FailedToGetResourcesException import dev.usbharu.hideout.core.domain.exception.HttpSignatureVerifyException import dev.usbharu.hideout.core.query.UserQueryService import dev.usbharu.hideout.util.RsaUtil +import dev.usbharu.httpsignature.common.HttpMethod import dev.usbharu.httpsignature.common.HttpRequest import dev.usbharu.httpsignature.common.PublicKey import dev.usbharu.httpsignature.verify.FailedVerification import dev.usbharu.httpsignature.verify.HttpSignatureVerifier +import dev.usbharu.httpsignature.verify.SignatureHeaderParser import kotlinx.coroutines.runBlocking import org.slf4j.LoggerFactory import org.springframework.security.authentication.BadCredentialsException @@ -20,14 +22,16 @@ import org.springframework.security.web.authentication.preauth.PreAuthenticatedA class HttpSignatureUserDetailsService( private val userQueryService: UserQueryService, private val httpSignatureVerifier: HttpSignatureVerifier, - private val transaction: Transaction + private val transaction: Transaction, + private val httpSignatureHeaderParser: SignatureHeaderParser ) : AuthenticationUserDetailsService { override fun loadUserDetails(token: PreAuthenticatedAuthenticationToken): UserDetails = runBlocking { if (token.principal !is String) { throw IllegalStateException("Token is not String") } - if (token.credentials !is HttpRequest) { + val credentials = token.credentials + if (credentials !is HttpRequest) { throw IllegalStateException("Credentials is not HttpRequest") } @@ -40,10 +44,25 @@ class HttpSignatureUserDetailsService( } } + val signature = httpSignatureHeaderParser.parse(credentials.headers) + + val requiredHeaders = when (credentials.method) { + HttpMethod.GET -> getRequiredHeaders + HttpMethod.POST -> postRequiredHeaders + } + if (signature.headers.containsAll(requiredHeaders).not()) { + logger.warn( + "FAILED Verify HTTP Signature. required headers: {} but actual: {}", + requiredHeaders, + signature.headers + ) + throw BadCredentialsException("HTTP Signature. required headers: $requiredHeaders") + } + @Suppress("TooGenericExceptionCaught") val verify = try { httpSignatureVerifier.verify( - token.credentials as HttpRequest, + credentials, PublicKey(RsaUtil.decodeRsaPublicKeyPem(findByKeyId.publicKey), keyId) ) } catch (e: RuntimeException) { @@ -67,5 +86,7 @@ class HttpSignatureUserDetailsService( companion object { private val logger = LoggerFactory.getLogger(HttpSignatureUserDetailsService::class.java) + private val postRequiredHeaders = listOf("(request-target)", "date", "host", "digest") + private val getRequiredHeaders = listOf("(request-target)", "date", "host") } } diff --git a/src/test/kotlin/dev/usbharu/hideout/activitypub/interfaces/api/inbox/InboxControllerImplTest.kt b/src/test/kotlin/dev/usbharu/hideout/activitypub/interfaces/api/inbox/InboxControllerImplTest.kt index 4fe4a3b2..b1f0b0cc 100644 --- a/src/test/kotlin/dev/usbharu/hideout/activitypub/interfaces/api/inbox/InboxControllerImplTest.kt +++ b/src/test/kotlin/dev/usbharu/hideout/activitypub/interfaces/api/inbox/InboxControllerImplTest.kt @@ -54,6 +54,7 @@ class InboxControllerImplTest { .post("/inbox") { content = json contentType = MediaType.APPLICATION_JSON + header("Signature", "") } .asyncDispatch() .andExpect { @@ -71,6 +72,7 @@ class InboxControllerImplTest { .post("/inbox") { content = json contentType = MediaType.APPLICATION_JSON + header("Signature", "") } .asyncDispatch() .andExpect { @@ -96,6 +98,7 @@ class InboxControllerImplTest { .post("/inbox") { content = json contentType = MediaType.APPLICATION_JSON + header("Signature", "") } .asyncDispatch() .andExpect { @@ -123,6 +126,7 @@ class InboxControllerImplTest { .post("/users/hoge/inbox") { content = json contentType = MediaType.APPLICATION_JSON + header("Signature", "") } .asyncDispatch() .andExpect { @@ -140,6 +144,7 @@ class InboxControllerImplTest { .post("/users/hoge/inbox") { content = json contentType = MediaType.APPLICATION_JSON + header("Signature", "") } .asyncDispatch() .andExpect { @@ -165,6 +170,7 @@ class InboxControllerImplTest { .post("/users/hoge/inbox") { content = json contentType = MediaType.APPLICATION_JSON + header("Signature", "") } .asyncDispatch() .andExpect { From 18e675367cf34c6a9beab632882c97f9648f40ce Mon Sep 17 00:00:00 2001 From: usbharu <64310155+usbharu@users.noreply.github.com> Date: Wed, 29 Nov 2023 16:53:01 +0900 Subject: [PATCH 5/6] style: fix lint --- .../usbharu/hideout/activitypub/domain/model/Delete.kt | 4 ++-- .../dev/usbharu/hideout/activitypub/domain/model/Undo.kt | 9 ++++----- .../usbharu/hideout/application/config/SecurityConfig.kt | 4 ---- .../hideout/core/domain/model/instance/Nodeinfo2_0.kt | 6 +++--- 4 files changed, 9 insertions(+), 14 deletions(-) diff --git a/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/model/Delete.kt b/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/model/Delete.kt index f2142722..6f691492 100644 --- a/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/model/Delete.kt +++ b/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/model/Delete.kt @@ -43,8 +43,8 @@ open class Delete : Object, HasId, HasActor { override fun hashCode(): Int { var result = super.hashCode() - result = 31 * result + (apObject?.hashCode() ?: 0) - result = 31 * result + (published?.hashCode() ?: 0) + result = 31 * result + apObject.hashCode() + result = 31 * result + published.hashCode() result = 31 * result + actor.hashCode() result = 31 * result + id.hashCode() return result diff --git a/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/model/Undo.kt b/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/model/Undo.kt index d02ccc4d..01dbc17c 100644 --- a/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/model/Undo.kt +++ b/src/main/kotlin/dev/usbharu/hideout/activitypub/domain/model/Undo.kt @@ -30,14 +30,13 @@ open class Undo( override fun hashCode(): Int { var result = super.hashCode() - result = 31 * result + (`object`?.hashCode() ?: 0) - result = 31 * result + (published?.hashCode() ?: 0) + result = 31 * result + `object`.hashCode() + result = 31 * result + published.hashCode() result = 31 * result + actor.hashCode() result = 31 * result + id.hashCode() return result } - override fun toString(): String { - return "Undo(`object`=$`object`, published=$published, actor='$actor', id='$id') ${super.toString()}" - } + override fun toString(): String = + "Undo(`object`=$`object`, published=$published, actor='$actor', id='$id') ${super.toString()}" } diff --git a/src/main/kotlin/dev/usbharu/hideout/application/config/SecurityConfig.kt b/src/main/kotlin/dev/usbharu/hideout/application/config/SecurityConfig.kt index 499e68dd..9801c6da 100644 --- a/src/main/kotlin/dev/usbharu/hideout/application/config/SecurityConfig.kt +++ b/src/main/kotlin/dev/usbharu/hideout/application/config/SecurityConfig.kt @@ -6,7 +6,6 @@ import com.nimbusds.jose.jwk.RSAKey import com.nimbusds.jose.jwk.source.ImmutableJWKSet import com.nimbusds.jose.jwk.source.JWKSource import com.nimbusds.jose.proc.SecurityContext -import dev.usbharu.hideout.activitypub.service.objects.user.APUserService import dev.usbharu.hideout.application.external.Transaction import dev.usbharu.hideout.core.infrastructure.springframework.httpsignature.HttpSignatureFilter import dev.usbharu.hideout.core.infrastructure.springframework.httpsignature.HttpSignatureUserDetailsService @@ -116,9 +115,6 @@ class SecurityConfig { @Bean fun getHttpSignatureFilter( authenticationManager: AuthenticationManager, - transaction: Transaction, - apUserService: APUserService, - userQueryService: UserQueryService ): HttpSignatureFilter { val httpSignatureFilter = HttpSignatureFilter(DefaultSignatureHeaderParser()) diff --git a/src/main/kotlin/dev/usbharu/hideout/core/domain/model/instance/Nodeinfo2_0.kt b/src/main/kotlin/dev/usbharu/hideout/core/domain/model/instance/Nodeinfo2_0.kt index 98247150..97478228 100644 --- a/src/main/kotlin/dev/usbharu/hideout/core/domain/model/instance/Nodeinfo2_0.kt +++ b/src/main/kotlin/dev/usbharu/hideout/core/domain/model/instance/Nodeinfo2_0.kt @@ -3,17 +3,17 @@ package dev.usbharu.hideout.core.domain.model.instance @Suppress("ClassNaming") -class Nodeinfo2_0() { +class Nodeinfo2_0 { var metadata: Metadata? = null var software: Software? = null } -class Metadata() { +class Metadata { var nodeName: String? = null var nodeDescription: String? = null } -class Software() { +class Software { var name: String? = null var version: String? = null } From dd8a08b84d9a49a31ec6e4b5af0539a4aad79b41 Mon Sep 17 00:00:00 2001 From: usbharu Date: Wed, 29 Nov 2023 16:55:29 +0900 Subject: [PATCH 6/6] Update integration-test.yml --- .github/workflows/integration-test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/integration-test.yml b/.github/workflows/integration-test.yml index 1788e609..ca866978 100644 --- a/.github/workflows/integration-test.yml +++ b/.github/workflows/integration-test.yml @@ -64,4 +64,4 @@ jobs: uses: mikepenz/action-junit-report@v2 if: always() with: - report_paths: '**/build/test-results/test/TEST-*.xml' + report_paths: '**/build/test-results/integrationTest/TEST-*.xml'