From d7d93f4856767387516442e885461ba164868585 Mon Sep 17 00:00:00 2001 From: ryousuke Date: Thu, 27 Jun 2024 10:49:48 +0900 Subject: [PATCH 1/4] Add for request test of OpenIdProviderTest.kt and change interface of verifyJwtByX5C from Either to Result --- .../oid/OpenIdProvider.kt | 50 +- .../tw2023_wallet_android/signature/JWT.kt | 10 +- .../CredentialVerifierTest.kt | 20 +- .../oid/OpenIdProviderTest.kt | 453 ++++++++++++------ 4 files changed, 347 insertions(+), 186 deletions(-) diff --git a/app/src/main/java/com/ownd_project/tw2023_wallet_android/oid/OpenIdProvider.kt b/app/src/main/java/com/ownd_project/tw2023_wallet_android/oid/OpenIdProvider.kt index 6b6035d..9b5bc8e 100644 --- a/app/src/main/java/com/ownd_project/tw2023_wallet_android/oid/OpenIdProvider.kt +++ b/app/src/main/java/com/ownd_project/tw2023_wallet_android/oid/OpenIdProvider.kt @@ -135,34 +135,28 @@ class OpenIdProvider(val uri: String, val option: SigningOption = SigningOption( } val clientScheme = payload.clientIdScheme ?: authorizationRequestPayload.clientIdScheme - val jwtValidationResult = - if (clientScheme == "x509_san_dns") { - val verifyResult = JWT.verifyJwtByX5C(requestObjectJwt) - verifyResult.fold( - ifLeft = { - // throw RuntimeException(it) - Either.Left("Invalid request") - }, - ifRight = { (decodedJwt, certificates) -> - // https://openid.net/specs/openid-4-verifiable-presentations-1_0.html - /* - the Client Identifier MUST be a DNS name and match a dNSName Subject Alternative Name (SAN) [RFC5280] entry in the leaf certificate passed with the request. - */ - if (!certificates[0].hasSubjectAlternativeName(clientId)) { - Either.Left("Invalid client_id or response_uri") - } - val uri = payload.responseUri ?: payload.redirectUri - if (clientId != uri) { - Either.Left("Invalid client_id or host uri") - } - decodedJwt - } - ) - } else { - val jwksUrl = registrationMetadata.jwksUri - ?: throw IllegalStateException("JWKS URLが見つかりません。") - JWT.verifyJwtWithJwks(requestObjectJwt, jwksUrl) + if (clientScheme == "x509_san_dns") { + val verifyResult = JWT.verifyJwtByX5C(requestObjectJwt) + if (!verifyResult.isSuccess) { + return Either.Left("Invalid request") + } + val (decodedJwt, certificates) = verifyResult.getOrThrow() + // https://openid.net/specs/openid-4-verifiable-presentations-1_0.html + /* + the Client Identifier MUST be a DNS name and match a dNSName Subject Alternative Name (SAN) [RFC5280] entry in the leaf certificate passed with the request. + */ + if (!certificates[0].hasSubjectAlternativeName(clientId)) { + return Either.Left("client_id is not in SAN entry") } + val uri = payload.responseUri ?: payload.redirectUri + if (clientId != uri) { + return Either.Left("Invalid client_id or host uri") + } + } else { + val jwksUrl = registrationMetadata.jwksUri + ?: throw IllegalStateException("JWKS URLが見つかりません。") + JWT.verifyJwtWithJwks(requestObjectJwt, jwksUrl) + } val result = try { if (clientScheme == "redirect_uri") { @@ -610,5 +604,5 @@ data class PostResult( fun X509Certificate.hasSubjectAlternativeName(target: String): Boolean { val altNames = this.subjectAlternativeNames ?: return false - return altNames.any { it[1] == target } + return altNames.any { target.contains(it[1].toString()) } } \ No newline at end of file diff --git a/app/src/main/java/com/ownd_project/tw2023_wallet_android/signature/JWT.kt b/app/src/main/java/com/ownd_project/tw2023_wallet_android/signature/JWT.kt index 25bb958..9525a80 100644 --- a/app/src/main/java/com/ownd_project/tw2023_wallet_android/signature/JWT.kt +++ b/app/src/main/java/com/ownd_project/tw2023_wallet_android/signature/JWT.kt @@ -119,7 +119,7 @@ class JWT { } } - fun verifyJwtByX5C(jwt: String): Either>> { + fun verifyJwtByX5C(jwt: String): Result>> { // https://www.rfc-editor.org/rfc/rfc7515.html#section-4.1.6 // https://www.rfc-editor.org/rfc/rfc7515.html#appendix-B val decodedJwt = JWT.decode(jwt) @@ -128,19 +128,19 @@ class JWT { val certificates = convertPemToX509Certificates(certs) if (certificates.isNullOrEmpty()) { - return Either.Left("証明書リストが取得できませんでした") + return Result.failure(Exception("証明書リストが取得できませんでした")) } val result = verifyJwt(jwt, certificates[0].publicKey) val b = validateCertificateChain(certificates, certificates.last()) // todo row to der エンコーディングの変換ができずjava.security.Signatureを使った実装が未対応(ES256Kサポートのためには対応が必要) return if (result.isRight() && b) { - Either.Right(Pair(decodedJwt, certificates)) + Result.success(Pair(decodedJwt, certificates)) } else { - Either.Left("JWTの検証に失敗しました") + Result.failure(Exception("JWTの検証に失敗しました")) } } catch (e: IOException) { println(e) - return Either.Left("JWTの検証に失敗しました") + return Result.failure(Exception("JWTの検証に失敗しました")) } } diff --git a/app/src/test/java/com/ownd_project/tw2023_wallet_android/CredentialVerifierTest.kt b/app/src/test/java/com/ownd_project/tw2023_wallet_android/CredentialVerifierTest.kt index 0fff574..22364b1 100644 --- a/app/src/test/java/com/ownd_project/tw2023_wallet_android/CredentialVerifierTest.kt +++ b/app/src/test/java/com/ownd_project/tw2023_wallet_android/CredentialVerifierTest.kt @@ -167,19 +167,13 @@ class CredentialVerifierTest { .withHeader(mapOf("x5c" to certs)) .sign(algorithm) val result = verifyJwtByX5C(token) - Assert.assertTrue(result.isRight()) - result.fold( - ifLeft = { - Assert.fail() - }, - ifRight = {(decodedJwt, certificates) -> - if (!certificates[0].hasSubjectAlternativeName("alt1.verifier.com")) { - Assert.fail() - } - val vc = decodedJwt.getClaim("vc") - Assert.assertNotNull(vc) - } - ) + Assert.assertTrue(result.isSuccess) + val (decodedJwt, certificates) = result.getOrThrow() + if (!certificates[0].hasSubjectAlternativeName("alt1.verifier.com")) { + Assert.fail() + } + val vc = decodedJwt.getClaim("vc") + Assert.assertNotNull(vc) } } diff --git a/app/src/test/java/com/ownd_project/tw2023_wallet_android/oid/OpenIdProviderTest.kt b/app/src/test/java/com/ownd_project/tw2023_wallet_android/oid/OpenIdProviderTest.kt index 4dd089f..41b0344 100644 --- a/app/src/test/java/com/ownd_project/tw2023_wallet_android/oid/OpenIdProviderTest.kt +++ b/app/src/test/java/com/ownd_project/tw2023_wallet_android/oid/OpenIdProviderTest.kt @@ -3,6 +3,8 @@ package com.ownd_project.tw2023_wallet_android.oid import android.util.Log import com.auth0.jwt.JWT import com.auth0.jwt.algorithms.Algorithm +import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper +import com.fasterxml.jackson.module.kotlin.readValue import com.ownd_project.tw2023_wallet_android.encodePublicKeyToJwks import com.ownd_project.tw2023_wallet_android.pairwise.HDKeyRing import com.ownd_project.tw2023_wallet_android.signature.SignatureUtil @@ -10,6 +12,7 @@ import com.ownd_project.tw2023_wallet_android.signature.ECPrivateJwk import com.ownd_project.tw2023_wallet_android.utils.generateRsaKeyPair import com.github.tomakehurst.wiremock.WireMockServer import com.github.tomakehurst.wiremock.client.WireMock +import com.ownd_project.tw2023_wallet_android.utils.generateEcKeyPair import junit.framework.TestCase.assertEquals import junit.framework.TestCase.assertNotNull import junit.framework.TestCase.assertTrue @@ -18,10 +21,16 @@ import kotlinx.coroutines.runBlocking import org.junit.After import org.junit.Before import org.junit.Test +import org.junit.experimental.runners.Enclosed +import org.junit.runner.RunWith import java.net.URLEncoder +import java.nio.charset.StandardCharsets import java.security.PrivateKey +import java.security.interfaces.ECPrivateKey +import java.security.interfaces.ECPublicKey import java.security.interfaces.RSAPrivateKey import java.security.interfaces.RSAPublicKey +import java.util.Base64 import java.util.Date const val clientHost = "http://localhost" @@ -37,20 +46,21 @@ fun createRequestObjectJwt(privateKey: PrivateKey, kid: String, clientId: String .withClaim("max_age", 86400).withIssuedAt(Date()).withKeyId(kid).sign(algorithm) } +@RunWith(Enclosed::class) class OpenIdProviderTest { - private val keyPair = generateRsaKeyPair() - private lateinit var wireMockServer: WireMockServer + class ResponseTest { + private val keyPair = generateRsaKeyPair() + private lateinit var wireMockServer: WireMockServer + @Before + fun setup() { + wireMockServer = WireMockServer().apply { + start() + WireMock.configureFor("localhost", port()) + } - @Before - fun setup() { - wireMockServer = WireMockServer().apply { - start() - WireMock.configureFor("localhost", port()) - } - - val jwksUrl = "$clientHost:${wireMockServer.port()}/.well-known/jwks.json" - val mockedResponse = """{ + val jwksUrl = "$clientHost:${wireMockServer.port()}/.well-known/jwks.json" + val mockedResponse = """{ "scopes_supported": ["openid"], "subject_types_supported": ["public"], "id_token_signing_alg_values_supported": ["RS256"], @@ -83,148 +93,311 @@ class OpenIdProviderTest { } }""" - wireMockServer.stubFor( - WireMock.get(WireMock.urlEqualTo("/client-metadata-uri")).willReturn( - WireMock.aResponse().withStatus(200).withBody(mockedResponse) + wireMockServer.stubFor( + WireMock.get(WireMock.urlEqualTo("/client-metadata-uri")).willReturn( + WireMock.aResponse().withStatus(200).withBody(mockedResponse) + ) ) - ) - wireMockServer.stubFor( - WireMock.post(WireMock.urlEqualTo("/cb")) - .withHeader("Content-Type", WireMock.equalTo("application/x-www-form-urlencoded")) - .willReturn( - WireMock.aResponse() - .withStatus(200) - .withBody("レスポンスの内容") + wireMockServer.stubFor( + WireMock.post(WireMock.urlEqualTo("/cb")) + .withHeader("Content-Type", WireMock.equalTo("application/x-www-form-urlencoded")) + .willReturn( + WireMock.aResponse() + .withStatus(200) + .withBody("レスポンスの内容") + ) + ) + + val publicKey = keyPair.public as RSAPublicKey + val jwksResponse = encodePublicKeyToJwks(publicKey, "test-kid") + + wireMockServer.stubFor( + WireMock.get(WireMock.urlEqualTo("/.well-known/jwks.json")).willReturn( + WireMock.aResponse().withStatus(200).withBody(jwksResponse) ) - ) + ) + } + + @After + fun teardown() { + wireMockServer.stop() + } - val publicKey = keyPair.public as RSAPublicKey - val jwksResponse = encodePublicKeyToJwks(publicKey, "test-kid") - wireMockServer.stubFor( - WireMock.get(WireMock.urlEqualTo("/.well-known/jwks.json")).willReturn( - WireMock.aResponse().withStatus(200).withBody(jwksResponse) + @Test + fun testSendRequestWithDirectPost() = runBlocking { + // MockWebServerに対するレスポンスを設定します。 + wireMockServer.stubFor( + WireMock.post(WireMock.urlEqualTo("/")) + .withHeader("Content-Type", WireMock.equalTo("application/x-www-form-urlencoded")) + .willReturn( + WireMock.aResponse() + .withStatus(200) + .withBody("response body") + ) ) - ) - } - @After - fun teardown() { - wireMockServer.stop() - } + // テスト対象のメソッドを呼び出します。 + val result = sendRequest("$clientHost:${wireMockServer.port()}/", mapOf("key" to "value"), ResponseMode.DIRECT_POST) + // レスポンスが期待通りであることを確認します。 + assertEquals(200, result.statusCode) - @Test - fun testSendRequestWithDirectPost() = runBlocking { - // MockWebServerに対するレスポンスを設定します。 - wireMockServer.stubFor( - WireMock.post(WireMock.urlEqualTo("/")) - .withHeader("Content-Type", WireMock.equalTo("application/x-www-form-urlencoded")) - .willReturn( - WireMock.aResponse() - .withStatus(200) - .withBody("response body") - ) - ) + // リクエストが期待通りであることを確認します。 + val recordedRequest = wireMockServer.findAll(WireMock.postRequestedFor(WireMock.urlEqualTo("/"))).first() + assertEquals("POST", recordedRequest.method.toString()) + assertEquals("key=value", recordedRequest.bodyAsString) + } + + @Test + fun testRespondIdTokenResponse() = runBlocking { + val clientMetadataUri = "$clientHost:${wireMockServer.port()}/client-metadata-uri" + val requestJwt = createRequestObjectJwt( + keyPair.private, + "test-kid", + "$clientHost:${wireMockServer.port()}/cb", + clientMetadataUri + ) + val uri = + "siopv2://?client_id=123&redirect_uri=123&request=$requestJwt" - // テスト対象のメソッドを呼び出します。 - val result = sendRequest("$clientHost:${wireMockServer.port()}/", mapOf("key" to "value"), ResponseMode.DIRECT_POST) + val op = OpenIdProvider(uri) + val result = op.processAuthorizationRequest() + result.fold( + ifLeft = { value -> + fail("エラーが発生しました: ${value}") + }, + ifRight = { value -> + val (scheme, requestObject, authorizationRequestPayload, requestObjectJwt, registrationMetadata) = value + // RequestObjectPayloadオブジェクトの内容を検証 + assertEquals("openid", requestObject?.scope) + assertEquals("code id_token", requestObject?.responseType) + assertEquals("$clientHost:${wireMockServer.port()}/cb", requestObject?.clientId) + assertEquals("$clientHost:${wireMockServer.port()}/cb", requestObject?.redirectUri) + // nonceとstateはランダムに生成される可能性があるため、存在することのみを確認 + assertNotNull(requestObject?.nonce) + assertNotNull(requestObject?.state) + assertEquals(86400, requestObject?.maxAge) - // レスポンスが期待通りであることを確認します。 - assertEquals(200, result.statusCode) + assertEquals("ClientName", registrationMetadata.clientName) + assertEquals("https://example.com/logo.png", registrationMetadata.logoUri) + }) - // リクエストが期待通りであることを確認します。 - val recordedRequest = wireMockServer.findAll(WireMock.postRequestedFor(WireMock.urlEqualTo("/"))).first() - assertEquals("POST", recordedRequest.method.toString()) - assertEquals("key=value", recordedRequest.bodyAsString) - } + // 結果の検証 + assertNotNull(result) + assertTrue(result.isRight()) + + // SIOP Response送信 + var keyRing = HDKeyRing(null) + val jwk = keyRing.getPrivateJwk(1) + val privateJwk = object : ECPrivateJwk { + override val kty = jwk.kty + override val crv = jwk.crv + override val x = jwk.x + override val y = jwk.y + override val d = jwk.d + } + val keyPair = SignatureUtil.generateECKeyPair(privateJwk) + op.setKeyPair(keyPair) + val responseResult = op.respondIdTokenResponse() + assertTrue(responseResult.isRight()) + } + + @Test + fun testMergeOAuth2AndOpenIdInRequestPayload() { + val payload = AuthorizationRequestPayloadImpl( + scope = "openid email", + responseType = "code", + clientId = "client123", + redirectUri = "https://client.example.com/cb", + nonce = "nonce123", + state = "state123", + clientIdScheme = "redirect_uri" + ) + + val requestObject = RequestObjectPayloadImpl( + iss = "issuer2", + aud = Audience.Single("aud2"), + responseType = "code id_token", + clientId = "client456", + redirectUri = "https://client.example.com/redirect", + idTokenHint = "idTokenHint", + clientIdScheme = "redirect_uri" + ) + + val mergedMap = mergeOAuth2AndOpenIdInRequestPayload(payload, requestObject) - @Test - fun testProcessSIOPRequest() = runBlocking { - val clientMetadataUri = "$clientHost:${wireMockServer.port()}/client-metadata-uri" - val requestJwt = createRequestObjectJwt( - keyPair.private, - "test-kid", - "$clientHost:${wireMockServer.port()}/cb", - clientMetadataUri - ) - val uri = - "siopv2://?client_id=123&redirect_uri=123&request=$requestJwt" - - val op = OpenIdProvider(uri) - val result = op.processAuthorizationRequest() - result.fold( - ifLeft = { value -> - fail("エラーが発生しました: ${value}") - }, - ifRight = { value -> - val (scheme, requestObject, authorizationRequestPayload, requestObjectJwt, registrationMetadata) = value - // RequestObjectPayloadオブジェクトの内容を検証 - assertEquals("openid", requestObject?.scope) - assertEquals("code id_token", requestObject?.responseType) - assertEquals("$clientHost:${wireMockServer.port()}/cb", requestObject?.clientId) - assertEquals("$clientHost:${wireMockServer.port()}/cb", requestObject?.redirectUri) - // nonceとstateはランダムに生成される可能性があるため、存在することのみを確認 - assertNotNull(requestObject?.nonce) - assertNotNull(requestObject?.state) - assertEquals(86400, requestObject?.maxAge) - - assertEquals("ClientName", registrationMetadata.clientName) - assertEquals("https://example.com/logo.png", registrationMetadata.logoUri) - }) - - // 結果の検証 - assertNotNull(result) - assertTrue(result.isRight()) - - // SIOP Response送信 - var keyRing = HDKeyRing(null) - val jwk = keyRing.getPrivateJwk(1) - val privateJwk = object : ECPrivateJwk { - override val kty = jwk.kty - override val crv = jwk.crv - override val x = jwk.x - override val y = jwk.y - override val d = jwk.d + // マージされたプロパティを検証 + assertEquals("issuer2", mergedMap.iss) + assertEquals(Audience.Single("aud2"), mergedMap.aud) + assertEquals("code id_token", mergedMap.responseType) + assertEquals("client456", mergedMap.clientId) + assertEquals("https://client.example.com/redirect", mergedMap.redirectUri) + assertEquals("idTokenHint", mergedMap.idTokenHint) + assertEquals("nonce123", mergedMap.nonce) + assertEquals("state123", mergedMap.state) } - val keyPair = SignatureUtil.generateECKeyPair(privateJwk) - op.setKeyPair(keyPair) - val responseResult = op.respondIdTokenResponse() - assertTrue(responseResult.isRight()) } - @Test - fun testMergeOAuth2AndOpenIdInRequestPayload() { - val payload = AuthorizationRequestPayloadImpl( - scope = "openid email", - responseType = "code", - clientId = "client123", - redirectUri = "https://client.example.com/cb", - nonce = "nonce123", - state = "state123", - clientIdScheme = "redirect_uri" - ) - - val requestObject = RequestObjectPayloadImpl( - iss = "issuer2", - aud = Audience.Single("aud2"), - responseType = "code id_token", - clientId = "client456", - redirectUri = "https://client.example.com/redirect", - idTokenHint = "idTokenHint", - clientIdScheme = "redirect_uri" - ) - - val mergedMap = mergeOAuth2AndOpenIdInRequestPayload(payload, requestObject) - - // マージされたプロパティを検証 - assertEquals("issuer2", mergedMap.iss) - assertEquals(Audience.Single("aud2"), mergedMap.aud) - assertEquals("code id_token", mergedMap.responseType) - assertEquals("client456", mergedMap.clientId) - assertEquals("https://client.example.com/redirect", mergedMap.redirectUri) - assertEquals("idTokenHint", mergedMap.idTokenHint) - assertEquals("nonce123", mergedMap.nonce) - assertEquals("state123", mergedMap.state) + class ProcessAuthorizationRequestTest { + private val keyPairTestCA = generateEcKeyPair() + private val keyPairTestIssuer = generateEcKeyPair() + lateinit var wireMockServer: WireMockServer + lateinit var clientMetadataMap: Map + lateinit var presentationDefinitionMap: Map + val algorithm = + Algorithm.ECDSA256( + keyPairTestIssuer.public as ECPublicKey, + keyPairTestIssuer.private as ECPrivateKey? + ) + + val clientMetadataJson = """{ + "scopes_supported": ["openid"], + "subject_types_supported": ["public"], + "id_token_signing_alg_values_supported": ["ES256"], + "request_object_signing_alg_values_supported": ["ES256"], + "subject_syntax_types_supported": ["syntax1", "syntax2"], + "request_object_encryption_alg_values_supported": ["ES256"], + "request_object_encryption_enc_values_supported": ["ES256"], + "client_name": "ClientName", + "logo_uri": "https://example.com/logo.png", + "client_purpose": "authentication" + }""" + + @Before + fun setup() { + wireMockServer = WireMockServer(8080) + wireMockServer.start() + WireMock.configureFor("localhost", 8080) + + val mapper = jacksonObjectMapper() + clientMetadataMap = mapper.readValue>(clientMetadataJson) + presentationDefinitionMap = mapper.readValue>(presentationDefinitionJson) + } + + @After + fun teardown() { + wireMockServer.stop() + } + + private fun prepareRequestUri(jwt: String): String? { + val requestUriPath = "/request-uri" + wireMockServer.stubFor( + WireMock.get(WireMock.urlEqualTo(requestUriPath)) + .willReturn( + WireMock.aResponse() + .withStatus(200) + .withBody(jwt) + ) + ) + + val requestUri = "http://localhost:${wireMockServer.port()}$requestUriPath" + return URLEncoder.encode(requestUri, StandardCharsets.UTF_8.toString()) + } + + val cert0 = SignatureUtil.generateCertificate(keyPairTestIssuer, keyPairTestCA, false, listOf("alt1.verifier.com")) + val encodedCert0 = Base64.getEncoder().encodeToString(cert0.encoded) + val cert1 = + SignatureUtil.generateCertificate(keyPairTestCA, keyPairTestCA, true) // 認証局は自己証明 + val encodedCert1 = Base64.getEncoder().encodeToString(cert1.encoded) + val certs = listOf(encodedCert0, encodedCert1) + + @Test + fun testClientIdSchemeX509SanDnsFailure() = runBlocking { + val clientId = "https://not-registered-in-san.verifier.com/cb" + val requestObjectJwt = JWT.create() + .withClaim("client_id_scheme", "x509_san_dns") + .withClaim("response_type", "vp_token") + .withClaim("response_uri", clientId) + .withClaim("client_metadata", clientMetadataMap) + .withClaim("presentation_definition", presentationDefinitionMap) + .withHeader(mapOf("x5c" to certs)) + .withExpiresAt(Date(System.currentTimeMillis() + 60 * 1000)) + .sign(algorithm) + val encodedJwtString = + URLEncoder.encode(requestObjectJwt, StandardCharsets.UTF_8.toString()) + val encodedRequestUri = prepareRequestUri(encodedJwtString) + + val uri = + "siopv2://?client_id=$clientId&request_uri=$encodedRequestUri" + + val op = OpenIdProvider(uri) + val result = op.processAuthorizationRequest() + println(result) + assertTrue(result.isLeft()) + } + + @Test + fun testClientIdSchemeX509SanDnsSuccess() = runBlocking { + val clientId = "https://alt1.verifier.com/cb" + val requestObjectJwt = JWT.create() + .withClaim("client_id_scheme", "x509_san_dns") + .withClaim("response_type", "vp_token") + .withClaim("response_uri", clientId) + .withClaim("client_metadata", clientMetadataMap) + .withClaim("presentation_definition", presentationDefinitionMap) + .withHeader(mapOf("x5c" to certs)) + .withExpiresAt(Date(System.currentTimeMillis() + 60 * 1000)) + .sign(algorithm) + val encodedJwtString = + URLEncoder.encode(requestObjectJwt, StandardCharsets.UTF_8.toString()) + val encodedRequestUri = prepareRequestUri(encodedJwtString) + + val uri = + "siopv2://?client_id=$clientId&request_uri=$encodedRequestUri" + + val op = OpenIdProvider(uri) + val result = op.processAuthorizationRequest() + println(result) + + assertTrue(result.isRight()) + } + + @Test + fun testClientIdSchemeRedirectUriFailure() = runBlocking { + val clientId = "https://www.verifier.com/cb" + val badRedirectUri = "https://bad-site.com/cb" + val encodedClientId = URLEncoder.encode(clientId, StandardCharsets.UTF_8.toString()) + val encodedBadRedirectUri = URLEncoder.encode(badRedirectUri, StandardCharsets.UTF_8.toString()) + val encodedClientMetadata = + URLEncoder.encode(clientMetadataJson, StandardCharsets.UTF_8.toString()) + val encodedPresentationDefinition = + URLEncoder.encode(presentationDefinitionJson, StandardCharsets.UTF_8.toString()) + val uri = + "openid4vp:///?client_id=${encodedClientId}" + + "&client_id_scheme=redirect_uri" + + "&response_type=vp_token" + + "&response_uri=${encodedBadRedirectUri}" + + "&client_metadata=${encodedClientMetadata}" + + "&presentation_definition=${encodedPresentationDefinition}" + val op = OpenIdProvider(uri) + val result = op.processAuthorizationRequest() + println(result) + + assertTrue(result.isLeft()) + } + + @Test + fun testClientIdSchemeRedirectUriSuccess() = runBlocking { + val clientId = "https://www.verifier.com/cb" + val encodedClientId = URLEncoder.encode(clientId, StandardCharsets.UTF_8.toString()) + val encodedClientMetadata = + URLEncoder.encode(clientMetadataJson, StandardCharsets.UTF_8.toString()) + val encodedPresentationDefinition = + URLEncoder.encode(presentationDefinitionJson, StandardCharsets.UTF_8.toString()) + val uri = + "openid4vp:///?client_id=${encodedClientId}" + + "&client_id_scheme=redirect_uri" + + "&response_type=vp_token" + + "&response_uri=${encodedClientId}" + + "&client_metadata=${encodedClientMetadata}" + + "&presentation_definition=${encodedPresentationDefinition}" + val op = OpenIdProvider(uri) + val result = op.processAuthorizationRequest() + println(result) + + assertTrue(result.isRight()) + } } -} \ No newline at end of file +} + From 3ff1dba77185f3e32875415a99bc920bd26bc4da Mon Sep 17 00:00:00 2001 From: ryousuke Date: Thu, 27 Jun 2024 13:03:38 +0900 Subject: [PATCH 2/4] Add for siop v2 request test of OpenIdProviderTest.kt --- .../oid/OpenIdProviderTest.kt | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/app/src/test/java/com/ownd_project/tw2023_wallet_android/oid/OpenIdProviderTest.kt b/app/src/test/java/com/ownd_project/tw2023_wallet_android/oid/OpenIdProviderTest.kt index 41b0344..e33bc42 100644 --- a/app/src/test/java/com/ownd_project/tw2023_wallet_android/oid/OpenIdProviderTest.kt +++ b/app/src/test/java/com/ownd_project/tw2023_wallet_android/oid/OpenIdProviderTest.kt @@ -398,6 +398,24 @@ class OpenIdProviderTest { assertTrue(result.isRight()) } + + @Test + fun testSIOPv2RedirectUriSuccess() = runBlocking { + val clientId = "https://www.verifier.com/cb" + val encodedClientId = URLEncoder.encode(clientId, StandardCharsets.UTF_8.toString()) + val encodedClientMetadata = + URLEncoder.encode(clientMetadataJson, StandardCharsets.UTF_8.toString()) + val uri = + "siopv2://?client_id=${encodedClientId}" + + "&response_type=id_token" + + "&response_uri=${encodedClientId}" + + "&client_metadata=${encodedClientMetadata}" + val op = OpenIdProvider(uri) + val result = op.processAuthorizationRequest() + println(result) + + assertTrue(result.isRight()) + } } } From 27c0a5dc34af8156e9ef3d562dd2dabe2bcfb490 Mon Sep 17 00:00:00 2001 From: ryousuke Date: Thu, 27 Jun 2024 14:40:57 +0900 Subject: [PATCH 3/4] Change interface from Either to Result --- .../oid/OpenIdProvider.kt | 24 +++++++++---------- .../ui/siop_vp/TokenSharingViewModel.kt | 6 ++--- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/app/src/main/java/com/ownd_project/tw2023_wallet_android/oid/OpenIdProvider.kt b/app/src/main/java/com/ownd_project/tw2023_wallet_android/oid/OpenIdProvider.kt index 9b5bc8e..230a920 100644 --- a/app/src/main/java/com/ownd_project/tw2023_wallet_android/oid/OpenIdProvider.kt +++ b/app/src/main/java/com/ownd_project/tw2023_wallet_android/oid/OpenIdProvider.kt @@ -105,7 +105,7 @@ class OpenIdProvider(val uri: String, val option: SigningOption = SigningOption( return this.siopRequest } - suspend fun processAuthorizationRequest(): Either { + suspend fun processAuthorizationRequest(): Result { if (uri.isBlank()) { throw IllegalArgumentException(SIOPErrors.BAD_PARAMS.message) } @@ -131,14 +131,14 @@ class OpenIdProvider(val uri: String, val option: SigningOption = SigningOption( val clientId = payload.clientId ?: authorizationRequestPayload.clientId if (clientId.isNullOrBlank()) { - return Either.Left("Invalid client_id or response_uri") + return Result.failure(Exception("Invalid client_id or response_uri")) } val clientScheme = payload.clientIdScheme ?: authorizationRequestPayload.clientIdScheme if (clientScheme == "x509_san_dns") { val verifyResult = JWT.verifyJwtByX5C(requestObjectJwt) if (!verifyResult.isSuccess) { - return Either.Left("Invalid request") + return Result.failure(Exception("Invalid request")) } val (decodedJwt, certificates) = verifyResult.getOrThrow() // https://openid.net/specs/openid-4-verifiable-presentations-1_0.html @@ -146,11 +146,11 @@ class OpenIdProvider(val uri: String, val option: SigningOption = SigningOption( the Client Identifier MUST be a DNS name and match a dNSName Subject Alternative Name (SAN) [RFC5280] entry in the leaf certificate passed with the request. */ if (!certificates[0].hasSubjectAlternativeName(clientId)) { - return Either.Left("client_id is not in SAN entry") + return Result.failure(Exception("client_id is not in SAN entry")) } val uri = payload.responseUri ?: payload.redirectUri if (clientId != uri) { - return Either.Left("Invalid client_id or host uri") + return Result.failure(Exception("Invalid client_id or host uri")) } } else { val jwksUrl = registrationMetadata.jwksUri @@ -162,11 +162,11 @@ class OpenIdProvider(val uri: String, val option: SigningOption = SigningOption( if (clientScheme == "redirect_uri") { val responseUri = payload.responseUri ?: authorizationRequestPayload.responseUri if (clientId.isNullOrBlank() || responseUri.isNullOrBlank() || clientId != responseUri) { - return Either.Left("Invalid client_id or response_uri") + return Result.failure(Exception("Invalid client_id or response_uri")) } } - Either.Right( + Result.success( ProcessSIOPRequestResult( scheme, payload, @@ -177,10 +177,10 @@ class OpenIdProvider(val uri: String, val option: SigningOption = SigningOption( ) ) } catch (e: JWTVerificationException) { - Either.Left(e.message ?: "JWT検証エラー") + Result.failure(e) } - if (result.isRight()) { - this.siopRequest = (result as Either.Right).value + if (result.isSuccess) { + this.siopRequest = result.getOrThrow() } return result } else { @@ -189,7 +189,7 @@ class OpenIdProvider(val uri: String, val option: SigningOption = SigningOption( val clientId = authorizationRequestPayload.clientId val responseUri = authorizationRequestPayload.responseUri if (clientId.isNullOrBlank() || responseUri.isNullOrBlank() || clientId != responseUri) { - return Either.Left("Invalid client_id or response_uri") + return Result.failure(Exception("Invalid client_id or response_uri")) } } val siopRequest = ProcessSIOPRequestResult( @@ -201,7 +201,7 @@ class OpenIdProvider(val uri: String, val option: SigningOption = SigningOption( presentationDefinition ) this.siopRequest = siopRequest - Either.Right(siopRequest) + Result.success(siopRequest) } } diff --git a/app/src/main/java/com/ownd_project/tw2023_wallet_android/ui/siop_vp/TokenSharingViewModel.kt b/app/src/main/java/com/ownd_project/tw2023_wallet_android/ui/siop_vp/TokenSharingViewModel.kt index b895ded..8c13a2b 100644 --- a/app/src/main/java/com/ownd_project/tw2023_wallet_android/ui/siop_vp/TokenSharingViewModel.kt +++ b/app/src/main/java/com/ownd_project/tw2023_wallet_android/ui/siop_vp/TokenSharingViewModel.kt @@ -166,14 +166,14 @@ class IdTokenSharringViewModel : ViewModel() { openIdProvider = OpenIdProvider(url, opt) val result = openIdProvider.processAuthorizationRequest() result.fold( - ifLeft = { value -> + onFailure = { value -> Log.e(TAG, "エラーが発生しました: ${value}") withContext(Dispatchers.Main) { _initDone.value = true - _errorMessage.value = value + _errorMessage.value = value.message } }, - ifRight = { siopRequest -> + onSuccess = { siopRequest -> Log.d(TAG, "processSiopRequest success") val (scheme, requestObject, authorizationRequestPayload, requestObjectJwt, registrationMetadata, presentationDefinition) = siopRequest val certificateInfo = From 387084436651cb7e6ec2526eb92411935ccb6377 Mon Sep 17 00:00:00 2001 From: ryousuke Date: Thu, 27 Jun 2024 14:48:06 +0900 Subject: [PATCH 4/4] Change interface from Either to Result --- .../oid/OpenIdProviderTest.kt | 18 +++++++++--------- .../oid/PresentationDefinitionTest.kt | 8 ++++---- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/app/src/test/java/com/ownd_project/tw2023_wallet_android/oid/OpenIdProviderTest.kt b/app/src/test/java/com/ownd_project/tw2023_wallet_android/oid/OpenIdProviderTest.kt index e33bc42..c298a7d 100644 --- a/app/src/test/java/com/ownd_project/tw2023_wallet_android/oid/OpenIdProviderTest.kt +++ b/app/src/test/java/com/ownd_project/tw2023_wallet_android/oid/OpenIdProviderTest.kt @@ -164,10 +164,10 @@ class OpenIdProviderTest { val op = OpenIdProvider(uri) val result = op.processAuthorizationRequest() result.fold( - ifLeft = { value -> - fail("エラーが発生しました: ${value}") + onFailure = { value -> + fail("エラーが発生しました: ${value.message}") }, - ifRight = { value -> + onSuccess = { value -> val (scheme, requestObject, authorizationRequestPayload, requestObjectJwt, registrationMetadata) = value // RequestObjectPayloadオブジェクトの内容を検証 assertEquals("openid", requestObject?.scope) @@ -185,7 +185,7 @@ class OpenIdProviderTest { // 結果の検証 assertNotNull(result) - assertTrue(result.isRight()) + assertTrue(result.isSuccess) // SIOP Response送信 var keyRing = HDKeyRing(null) @@ -324,7 +324,7 @@ class OpenIdProviderTest { val op = OpenIdProvider(uri) val result = op.processAuthorizationRequest() println(result) - assertTrue(result.isLeft()) + assertTrue(result.isFailure) } @Test @@ -350,7 +350,7 @@ class OpenIdProviderTest { val result = op.processAuthorizationRequest() println(result) - assertTrue(result.isRight()) + assertTrue(result.isSuccess) } @Test @@ -374,7 +374,7 @@ class OpenIdProviderTest { val result = op.processAuthorizationRequest() println(result) - assertTrue(result.isLeft()) + assertTrue(result.isFailure) } @Test @@ -396,7 +396,7 @@ class OpenIdProviderTest { val result = op.processAuthorizationRequest() println(result) - assertTrue(result.isRight()) + assertTrue(result.isSuccess) } @Test @@ -414,7 +414,7 @@ class OpenIdProviderTest { val result = op.processAuthorizationRequest() println(result) - assertTrue(result.isRight()) + assertTrue(result.isSuccess) } } } diff --git a/app/src/test/java/com/ownd_project/tw2023_wallet_android/oid/PresentationDefinitionTest.kt b/app/src/test/java/com/ownd_project/tw2023_wallet_android/oid/PresentationDefinitionTest.kt index aac70f5..f0401bd 100644 --- a/app/src/test/java/com/ownd_project/tw2023_wallet_android/oid/PresentationDefinitionTest.kt +++ b/app/src/test/java/com/ownd_project/tw2023_wallet_android/oid/PresentationDefinitionTest.kt @@ -359,10 +359,10 @@ class PresentationDefinitionTest { val op = OpenIdProvider(uri) val result = op.processAuthorizationRequest() result.fold( - ifLeft = { value -> - TestCase.fail("エラーが発生しました: ${value}") + onFailure = { value -> + TestCase.fail("エラーが発生しました: ${value.message}") }, - ifRight = { value -> + onSuccess = { value -> val (scheme, requestObject, authorizationRequestPayload, requestObjectJwt, registrationMetadata, presentationDefinition) = value // RequestObjectPayloadオブジェクトの内容を検証 TestCase.assertEquals("openid", requestObject?.scope) @@ -388,7 +388,7 @@ class PresentationDefinitionTest { // 結果の検証 TestCase.assertNotNull(result) - TestCase.assertTrue(result.isRight()) + TestCase.assertTrue(result.isSuccess) val disclosure1 = Disclosure("claim1", "value1") val disclosure2 = Disclosure("claim2", "value2")