Skip to content

Commit

Permalink
Merge pull request #263 from walt-id/update_policy_verification
Browse files Browse the repository at this point in the history
update_policy_verification
  • Loading branch information
waltkb authored Mar 22, 2023
2 parents 6677a9a + 60b74ed commit 7d08fff
Show file tree
Hide file tree
Showing 46 changed files with 220 additions and 162 deletions.
4 changes: 2 additions & 2 deletions src/main/kotlin/id/walt/auditor/Auditor.kt
Original file line number Diff line number Diff line change
Expand Up @@ -41,14 +41,14 @@ class WaltIdAuditor : Auditor() {
log.debug { "Verifying vc with ${policy.id} ..." }

val vcResult = policy.verify(vc)
val success = AtomicBoolean(vcResult.outcome)
val success = AtomicBoolean(vcResult.result)
val allErrors = vcResult.errors.toMutableList()
if (allErrors.isEmpty() && vc is VerifiablePresentation) {
vc.verifiableCredential?.forEach { cred ->
log.debug { "Verifying ${cred.type.last()} in VP with ${policy.id}..." }
val vpResult = policy.verify(cred)
allErrors.addAll(vpResult.errors)
success.compareAndSet(true, vpResult.outcome)
success.compareAndSet(true, vpResult.result)
}
}
allErrors.forEach { log.error { "${policy.id}: $it" } }
Expand Down
79 changes: 46 additions & 33 deletions src/main/kotlin/id/walt/auditor/VerificationPolicy.kt
Original file line number Diff line number Diff line change
Expand Up @@ -36,19 +36,27 @@ data class VerificationPolicyMetadata(
val isMutable: Boolean
)

class VerificationPolicyResult(val outcome: Boolean, val errors: List<Any> = listOf()) {
data class VerificationPolicyResult(val result: Boolean, val errors: List<Throwable> = listOf()) {
companion object {
fun success() = VerificationPolicyResult(true)
fun failure(error: Any) = VerificationPolicyResult(false, listOf(error))
fun failure(errors: List<Any> = listOf()) = VerificationPolicyResult(false, errors.toList())
fun failure(error: Throwable): VerificationPolicyResult {
log.debug { "VerificationPolicy failed: ${error.stackTraceToString()}" }
return VerificationPolicyResult(false, listOf(error))
}
fun failure(errors: List<Throwable> = listOf()) = VerificationPolicyResult(false, errors.toList())
}
val isSuccess = outcome
val isFailure = !outcome

val isSuccess = result
val isFailure = !result

fun getErrorString() = errors.mapIndexed { index, throwable ->
"#${index + 1}: ${throwable::class.simpleName ?: "Error"} - ${throwable.message}"
}.joinToString()

override fun toString(): String {
return when(outcome) {
true -> "true"
false -> "false $errors"
return when (result) {
true -> "passed"
false -> "failed: ${getErrorString()}"
}
}
}
Expand Down Expand Up @@ -81,28 +89,31 @@ class SignaturePolicy : SimpleVerificationPolicy() {
override val description: String = "Verify by signature"
override fun doVerify(vc: VerifiableCredential) = runCatching {
log.debug { "is jwt: ${vc.jwt != null}" }
VerificationPolicyResult(when (vc.jwt) {
null -> jsonLdCredentialService.verify(vc.encode()).verified
else -> jwtCredentialService.verify(vc.encode()).verified
})
}.onFailure {
log.error(it.localizedMessage)
}.getOrDefault(VerificationPolicyResult.failure())
VerificationPolicyResult(
when (vc.jwt) {
null -> jsonLdCredentialService.verify(vc.encode()).verified
else -> jwtCredentialService.verify(vc.encode()).verified
}
)
}.getOrElse {
VerificationPolicyResult.failure(it)
}
}

/**
* @param schema URL, file path or content of JSON schema to validate against
*/
data class JsonSchemaPolicyArg(val schema: String)

class JsonSchemaPolicy(schemaPolicyArg: JsonSchemaPolicyArg?) : OptionalParameterizedVerificationPolicy<JsonSchemaPolicyArg>(schemaPolicyArg) {
class JsonSchemaPolicy(schemaPolicyArg: JsonSchemaPolicyArg?) :
OptionalParameterizedVerificationPolicy<JsonSchemaPolicyArg>(schemaPolicyArg) {
constructor() : this(null)

override val description: String = "Verify by JSON schema"
override fun doVerify(vc: VerifiableCredential): VerificationPolicyResult {
return (argument?.schema ?: vc.credentialSchema?.id)?.let {
SchemaValidatorFactory.get(it).validate(vc.toJson())
} ?: VerificationPolicyResult.failure()
} ?: VerificationPolicyResult.failure(IllegalArgumentException("No \"argument.schema\" or \"credentialSchema.id\" supplied."))
}

override val applyToVP: Boolean
Expand All @@ -123,8 +134,11 @@ class TrustedIssuerDidPolicy : SimpleVerificationPolicy() {
return try {
VerificationPolicyResult(DidService.loadOrResolveAnyDid(vc.issuerId!!) != null)
} catch (e: ClientRequestException) {
if (!e.message.contains("did must be a valid DID") && !e.message.contains("Identifier Not Found")) throw e
VerificationPolicyResult.failure()
VerificationPolicyResult.failure(IllegalArgumentException(when {
"did must be a valid DID" in e.message -> "did must be a valid DID"
"Identifier Not Found" in e.message -> "Identifier Not Found"
else -> throw e
}))
}
}
}
Expand Down Expand Up @@ -153,11 +167,10 @@ class TrustedIssuerRegistryPolicy(registryArg: TrustedIssuerRegistryPolicyArg) :
val issuerDid = vc.issuerId!!

val resolvedIssuerDid = DidService.loadOrResolveAnyDid(issuerDid)
?: throw Exception("Could not resolve issuer DID $issuerDid")
?: throw IllegalArgumentException("Could not resolve issuer DID $issuerDid")

if (resolvedIssuerDid.id != issuerDid) {
log.debug { "Resolved DID ${resolvedIssuerDid.id} does not match the issuer DID $issuerDid" }
return VerificationPolicyResult.failure()
return VerificationPolicyResult.failure(IllegalArgumentException("Resolved DID ${resolvedIssuerDid.id} does not match the issuer DID $issuerDid"))
}
var tirRecord: TrustedIssuer

Expand Down Expand Up @@ -249,17 +262,17 @@ class CredentialStatusPolicy : SimpleVerificationPolicy() {
override fun doVerify(vc: VerifiableCredential): VerificationPolicyResult {
val cs = Klaxon().parse<CredentialStatusCredential>(vc.toJson())!!.credentialStatus!!

return VerificationPolicyResult(when (cs.type) {
fun revocationVerificationPolicy(revoked: Boolean, timeOfRevocation: Long?) =
if (!revoked) VerificationPolicyResult.success() else VerificationPolicyResult.failure(IllegalArgumentException("CredentialStatus (type ${cs.type}) was REVOKED at timestamp $timeOfRevocation for id ${cs.id}."))

return when (cs.type) {
"SimpleCredentialStatus2022" -> {
val rs = RevocationClientService.getService()
val result = rs.checkRevoked(cs.id)
!result.isRevoked
}

else -> {
throw IllegalArgumentException("CredentialStatus type \"\"")
revocationVerificationPolicy(result.isRevoked, result.timeOfRevocation)
}
})
else -> VerificationPolicyResult.failure(UnsupportedOperationException("CredentialStatus type \"${cs.type}\" is not yet supported."))
}
}
}

Expand Down Expand Up @@ -345,12 +358,12 @@ data class VerificationResult(
/***
* Validation status over all policy results.
*/
val outcome: Boolean = false,
val result: Boolean = false,
val policyResults: Map<String, VerificationPolicyResult>
) {
@Deprecated("Deprecated in favour of: outcome")
val valid: Boolean = outcome
@Deprecated("Deprecated in favour of: result")
val valid: Boolean = result

override fun toString() =
"VerificationResult(outcome=$outcome, policyResults={${policyResults.entries.joinToString { it.key + "=" + it.value }}})"
"VerificationResult(result=$result, policyResults={${policyResults.entries.joinToString { it.key + "=" + it.value }}})"
}
2 changes: 1 addition & 1 deletion src/main/kotlin/id/walt/auditor/dynamic/OPAPolicyEngine.kt
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ object OPAPolicyEngine : PolicyEngine {
try {
ProcessBuilder("opa").start()
} catch (e: Exception) {
return VerificationPolicyResult.failure("Executable for OPA policy engine not installed. See https://www.openpolicyagent.org/docs/#running-opa")
return VerificationPolicyResult.failure(IllegalStateException("Executable for OPA policy engine not installed. See https://www.openpolicyagent.org/docs/#running-opa"))
}
val regoFile = resolveContentToFile(policy, tempPrefix = TEMP_PREFIX, tempPostfix = ".rego")
val dataFile = File.createTempFile("data", ".json")
Expand Down
5 changes: 3 additions & 2 deletions src/main/kotlin/id/walt/cli/EssifCommand.kt
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import java.io.File
import java.util.*
import kotlin.NoSuchElementException

class EssifCommand : CliktCommand(
name = "essif",
Expand Down Expand Up @@ -119,7 +120,7 @@ class EssifTimestampCreateCommand : CliktCommand(
override fun run() {
echo("Creating timestamp")

if (!dataFile.exists()) throw Exception("File ${dataFile.absoluteFile} not found.")
if (!dataFile.exists()) throw NoSuchElementException("File ${dataFile.absoluteFile} not found.")

val transactionHash =
WaltIdTimestampService().createTimestamp(did, ethKeyAlias ?: did, "{\"test\": \"${UUID.randomUUID()}\"}")
Expand All @@ -146,7 +147,7 @@ class EssifTimestampGetCommand : CliktCommand(
when {
id != null -> WaltIdTimestampService().getByTimestampId(id!!)
txhash != null -> WaltIdTimestampService().getByTransactionHash(txhash!!)
else -> throw Exception("Either timestamp ID or transaction hash need to be specified")
else -> throw IllegalArgumentException("Either timestamp ID or transaction hash need to be specified")
}
}

Expand Down
6 changes: 3 additions & 3 deletions src/main/kotlin/id/walt/cli/VcCommand.kt
Original file line number Diff line number Diff line change
Expand Up @@ -201,8 +201,8 @@ class VerifyVcCommand : CliktCommand(
echo("Verifying from file \"$src\"...\n")

when {
!src.exists() -> throw Exception("Could not load file: \"$src\".")
usedPolicies.keys.any { !PolicyRegistry.contains(it) } -> throw Exception(
!src.exists() -> throw NoSuchElementException("Could not load file: \"$src\".")
usedPolicies.keys.any { !PolicyRegistry.contains(it) } -> throw NoSuchElementException(
"Unknown verification policy specified: ${
usedPolicies.keys.minus(PolicyRegistry.listPolicies().toSet()).joinToString()
}"
Expand All @@ -219,7 +219,7 @@ class VerifyVcCommand : CliktCommand(
verificationResult.policyResults.forEach { (policy, result) ->
echo("$policy:\t $result")
}
echo("Verified:\t\t ${verificationResult.outcome}")
echo("Verified:\t\t ${verificationResult.result}")
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/main/kotlin/id/walt/common/FileFun.kt
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ fun readWhenContent(file: File, errorMessage: String? = null) = readWhenContent(

fun readWhenContent(file: Path, errorMessage: String? = null) = when {
file.exists() && file.fileSize() > 0 -> file.readText()
else -> throw Exception(errorMessage ?: "Expecting file with content at: ${file.absolutePathString()}")
else -> throw NoSuchElementException(errorMessage ?: "Expecting file with content at: ${file.absolutePathString()}")
}

fun readEssifBearerToken(): String = readWhenContent(
Expand Down
8 changes: 4 additions & 4 deletions src/main/kotlin/id/walt/common/OidcUtil.kt
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ object OidcUtil {
if (jwtService.verify(authRequestJwt)) {
log.debug { "Successfully verified signature of JWT" }
} else {
throw Exception("Could not verify JWT $authRequestJwt")
throw IllegalArgumentException("Could not verify JWT $authRequestJwt")
}

//val didAuthRequestStr = String(decBase64(authRequestJwt))
Expand All @@ -115,15 +115,15 @@ object OidcUtil {
val request = Klaxon().parse<DidAuthRequest>(jwt.payload.toString())!!

if (oidcAuthReq.callback != request.callback) {
throw Exception("Callbacks in OidcRequest data structure are not matching we got: ${oidcAuthReq.callback} & ${request.callback}")
throw IllegalArgumentException("Callbacks in OidcRequest data structure are not matching we got: ${oidcAuthReq.callback} & ${request.callback}")
}

if (scope != request.scope) {
throw Exception("Scopes in OidcRequest data structure are not matching we got: $scope & ${request.scope}")
throw IllegalArgumentException("Scopes in OidcRequest data structure are not matching we got: $scope & ${request.scope}")
}

if (responseType != request.response_type) {
throw Exception("Scopes in OidcRequest data structure are not matching we got: $responseType & ${request.response_type}")
throw IllegalArgumentException("Scopes in OidcRequest data structure are not matching we got: $responseType & ${request.response_type}")
}

return request
Expand Down
2 changes: 1 addition & 1 deletion src/main/kotlin/id/walt/common/SerializationUtils.kt
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ val didVerificationRelationshipsConverter = object : Converter {
when (item) {
is String -> VerificationMethod.Reference(item)
is JsonObject -> Klaxon().parseFromJsonObject<VerificationMethod>(item)
else -> throw Exception("Verification relationship must be either String or JsonObject")
else -> throw IllegalArgumentException("Verification relationship must be either String or JsonObject")
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/main/kotlin/id/walt/credentials/w3c/JsonConverter.kt
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ object JsonConverter {
is JsonElement -> value

//else -> JsonNull
else -> throw Exception("Json values can only be Number, String, Boolean, Null, List or Map, not \"${value::class.jvmName}\": toString = $value")
else -> throw IllegalArgumentException("Json values can only be Number, String, Boolean, Null, List or Map, not \"${value::class.jvmName}\": toString = $value")
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ class VerifiablePresentation internal constructor(jsonObject: JsonObject) : Veri
when (it) {
is JsonPrimitive -> VerifiableCredential.fromString(it.content)
is JsonObject -> VerifiableCredential.fromJsonObject(it)
else -> throw Exception("Invalid type of verifiableCredential item")
else -> throw IllegalArgumentException("Invalid type of verifiableCredential item")
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ class W3CCredentialSchema(

fun fromJsonObject(jsonObject: JsonObject): W3CCredentialSchema {
return W3CCredentialSchema(
id = jsonObject["id"]?.jsonPrimitive?.content ?: throw Exception("Missing id property in CredentialSchema"),
type = jsonObject["type"]?.jsonPrimitive?.content ?: throw Exception("Missing id property in CredentialSchema"),
id = jsonObject["id"]?.jsonPrimitive?.content ?: throw IllegalArgumentException("Missing id property in CredentialSchema"),
type = jsonObject["type"]?.jsonPrimitive?.content ?: throw IllegalArgumentException("Missing id property in CredentialSchema"),
properties = jsonObject.filterKeys { k -> !W3CProof.PREDEFINED_PROPERTY_KEYS.contains(k) }
.mapValues { entry -> JsonConverter.fromJsonElement(entry.value) }
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import com.networknt.schema.JsonSchemaFactory
import com.networknt.schema.SpecVersion
import id.walt.auditor.VerificationPolicyResult
import mu.KotlinLogging
import javax.naming.directory.SchemaViolationException

class NetworkntSchemaValidator(versionFlag: SpecVersion.VersionFlag, schema: String) : SchemaValidator {
private val log = KotlinLogging.logger {}
Expand All @@ -18,6 +19,6 @@ class NetworkntSchemaValidator(versionFlag: SpecVersion.VersionFlag, schema: Str
log.debug { "Could not validate vc against schema. The validation errors are:" }
errors.forEach { log.debug { it } }
}
return VerificationPolicyResult(errors.isEmpty(), errors)
return VerificationPolicyResult(errors.isEmpty(), errors.map { SchemaViolationException(it.toString()) })
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package id.walt.credentials.w3c.schema
import id.walt.auditor.VerificationPolicyResult
import mu.KotlinLogging
import net.pwall.json.schema.JSONSchema
import javax.naming.directory.SchemaViolationException

class PWallSchemaValidator(schema: String) : SchemaValidator {
private val log = KotlinLogging.logger {}
Expand All @@ -15,6 +16,8 @@ class PWallSchemaValidator(schema: String) : SchemaValidator {
log.debug { "Could not validate vc against schema. The validation errors are:" }
errors.forEach { log.debug { it } }
}
return VerificationPolicyResult(errors.isEmpty(), errors)
return VerificationPolicyResult(errors.isEmpty(), errors.map {
SchemaViolationException(it.error)
})
}
}
2 changes: 1 addition & 1 deletion src/main/kotlin/id/walt/crypto/Key.kt
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ data class Key(val keyId: KeyId, val algorithm: KeyAlgorithm, val cryptoProvider
fun getPublicKey(): PublicKey = when {
keyPair != null -> keyPair!!.public
keysetHandle != null -> TinkKeyStoreService().loadPublicKey(this) as ECPublicKey
else -> throw Exception("No public key for $keyId")
else -> throw NoSuchElementException("No public key for $keyId")
}

fun getPublicKeyBytes(): ByteArray {
Expand Down
2 changes: 1 addition & 1 deletion src/main/kotlin/id/walt/crypto/UVarInt.kt
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ class UVarInt(val value: UInt) {

fun fromBytes(bytes: ByteArray): UVarInt {
if (bytes.isEmpty())
throw Exception("Empty byte array")
throw IllegalArgumentException("Empty byte array")

var idx = 0
var value = (bytes[idx].toUInt() and LSB)
Expand Down
2 changes: 1 addition & 1 deletion src/main/kotlin/id/walt/model/oidc/IDToken.kt
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ data class IDToken(
) {
fun sign(): String {
return subject?.let {JwtService.getService().sign(it, Klaxon().toJsonString(this)) } ?:
throw Exception("No subject specified")
throw IllegalArgumentException("No subject specified")
}

fun verify(): Boolean {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,8 @@ data class IssuanceInitiationRequest(
companion object {
fun fromQueryParams(params: Map<String, List<String>>): IssuanceInitiationRequest {
return IssuanceInitiationRequest(
issuer_url = params["issuer"]?.firstOrNull() ?: throw Exception("Missing parameter 'issuer'"),
credential_types = params["credential_type"] ?: throw Exception("Missing parameter(s) 'credential_type'"),
issuer_url = params["issuer"]?.firstOrNull() ?: throw IllegalArgumentException("Missing parameter 'issuer'"),
credential_types = params["credential_type"] ?: throw IllegalArgumentException("Missing parameter(s) 'credential_type'"),
pre_authorized_code = params["pre-authorized_code"]?.firstOrNull(),
user_pin_required = params["user_pin_required"]?.map { it.toBoolean() }?.firstOrNull() ?: false,
op_state = params["op_state"]?.firstOrNull()
Expand Down
4 changes: 2 additions & 2 deletions src/main/kotlin/id/walt/model/oidc/SIOPv2Response.kt
Original file line number Diff line number Diff line change
Expand Up @@ -53,14 +53,14 @@ data class SIOPv2Response(

companion object {
fun fromFormParams(params: Map<String, String>): SIOPv2Response {
val vpTokenStr = params["vp_token"] ?: throw Exception("vp_token parameter must be set")
val vpTokenStr = params["vp_token"] ?: throw IllegalArgumentException("vp_token parameter must be set")
val idToken = params["id_token"]
val presentationSubmissionStr = params["presentation_submission"]
return SIOPv2Response(
vp_token = OIDCUtils.fromVpToken(vpTokenStr),
presentation_submission = presentationSubmissionStr?.let { KlaxonWithConverters().parse<PresentationSubmission>(it) }
?: idToken?.let { IDToken.parse(it)?.vpTokenRef?.presentation_submission }
?: throw Exception("Could not parse presentation_submission parameter"),
?: throw IllegalArgumentException("Could not parse presentation_submission parameter"),
id_token = idToken,
state = params["state"]
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ object AuditorRestController {
Auditor.getService().verify(cred, policies)
}

ctx.json(VerificationResponse(valid = results.all { it.outcome }, results = results))
ctx.json(VerificationResponse(valid = results.all { it.result }, results = results))
}

fun verifyVPDocs() = document()
Expand Down
Loading

0 comments on commit 7d08fff

Please sign in to comment.