Skip to content

Commit

Permalink
Merge pull request #11 from ryosuke-wakaba/chore/add-test-case
Browse files Browse the repository at this point in the history
Added Pattern Tests for Authorization Request in OpenIdProviderTest
  • Loading branch information
moratori authored Jul 1, 2024
2 parents a5cde98 + 3870844 commit 1023914
Show file tree
Hide file tree
Showing 6 changed files with 381 additions and 202 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ class OpenIdProvider(val uri: String, val option: SigningOption = SigningOption(
return this.siopRequest
}

suspend fun processAuthorizationRequest(): Either<String, ProcessSIOPRequestResult> {
suspend fun processAuthorizationRequest(): Result<ProcessSIOPRequestResult> {
if (uri.isBlank()) {
throw IllegalArgumentException(SIOPErrors.BAD_PARAMS.message)
}
Expand All @@ -131,48 +131,42 @@ 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

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 Result.failure(Exception("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 Result.failure(Exception("client_id is not in SAN entry"))
}
val uri = payload.responseUri ?: payload.redirectUri
if (clientId != uri) {
return Result.failure(Exception("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") {
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,
Expand All @@ -183,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 {
Expand All @@ -195,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(
Expand All @@ -207,7 +201,7 @@ class OpenIdProvider(val uri: String, val option: SigningOption = SigningOption(
presentationDefinition
)
this.siopRequest = siopRequest
Either.Right(siopRequest)
Result.success(siopRequest)
}
}

Expand Down Expand Up @@ -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()) }
}
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ class JWT {
}
}

fun verifyJwtByX5C(jwt: String): Either<String, Pair<DecodedJWT, Array<X509Certificate>>> {
fun verifyJwtByX5C(jwt: String): Result<Pair<DecodedJWT, Array<X509Certificate>>> {
// 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)
Expand All @@ -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の検証に失敗しました"))
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}

Loading

0 comments on commit 1023914

Please sign in to comment.