Skip to content

Commit

Permalink
Fix #44: Signature validation support of DID starting with did:ebsi
Browse files Browse the repository at this point in the history
  • Loading branch information
josmilan committed Jul 23, 2024
1 parent 5827aac commit 4b85c60
Show file tree
Hide file tree
Showing 3 changed files with 151 additions and 8 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package com.ewc.eudi_wallet_oidc_android.models

import com.google.gson.annotations.SerializedName

data class DIDDocument(
@SerializedName("@context")
val context: List<String>,

@SerializedName("id")
val id: String,

@SerializedName("controller")
val controller: List<String>,

@SerializedName("verificationMethod")
val verificationMethods: List<VerificationMethod>,

@SerializedName("authentication")
val authentication: List<String>,

@SerializedName("assertionMethod")
val assertionMethods: List<String>,

@SerializedName("capabilityInvocation")
val capabilityInvocations: List<String>
)

data class VerificationMethod(
@SerializedName("id")
val id: String,

@SerializedName("type")
val type: String,

@SerializedName("controller")
val controller: String,

@SerializedName("publicKeyJwk")
val publicKeyJwk: PublicKeyJwk
)

data class PublicKeyJwk(
@SerializedName("kty")
val kty: String,

@SerializedName("crv")
val crv: String,

@SerializedName("x")
val x: String,

@SerializedName("y")
val y: String
)
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
package com.ewc.eudi_wallet_oidc_android.services.credentialValidation

import com.ewc.eudi_wallet_oidc_android.models.DIDDocument
import com.ewc.eudi_wallet_oidc_android.models.JwkKey
import com.ewc.eudi_wallet_oidc_android.models.JwksResponse
import com.ewc.eudi_wallet_oidc_android.services.exceptions.SignatureException
import com.ewc.eudi_wallet_oidc_android.services.did.DIDService
import com.ewc.eudi_wallet_oidc_android.services.network.ApiManager
import com.google.gson.Gson
import com.nimbusds.jose.JWSObject
import com.nimbusds.jose.crypto.ECDSAVerifier
Expand All @@ -14,6 +16,13 @@ import com.nimbusds.jose.util.Base64URL
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import java.net.URL
import com.google.gson.JsonArray
import com.google.gson.JsonParser
import com.nimbusds.jose.JOSEException
import com.nimbusds.jose.JWSAlgorithm
import com.nimbusds.jose.JWSVerifier
import retrofit2.Response
import java.text.ParseException

class SignatureValidator {

Expand All @@ -34,10 +43,14 @@ class SignatureValidator {
val jwsObject = JWSObject.parse(jwt)
val header = jwsObject.header
val kid = header.keyID

// Check the format of kid and process accordingly
val response = if (kid.startsWith("did:key:z")) {
val response = if ( kid !=null && kid.startsWith("did:key:z")) {
processJWKFromKID(kid)
} else {
} else if ( kid !=null && kid.startsWith("did:ebsi:z")){
processEbsiJWKFromKID(kid)
}
else {
processJWKFromJwksUri(kid,jwksUri)
}
if (response != null) {
Expand All @@ -52,6 +65,64 @@ class SignatureValidator {
}
}

// This function fetches and processes the DID Document, and extracts the P-256 JWK if present.
private suspend fun processEbsiJWKFromKID(did: String?): ECKey? {
return try {
// Validate DID format
if (did == null || !did.startsWith("did:ebsi:z")) {
throw IllegalArgumentException("Invalid DID format")
}

// Fetch the DID document from the API
val response:Response<DIDDocument> = ApiManager.api.getService()?.ebsiDIDResolver(
"https://api-conformance.ebsi.eu/did-registry/v5/identifiers/$did"
) ?: throw IllegalStateException("API service not available")

// Extract the P-256 JWK from the JSON response
val didDocument = response.body() ?: throw IllegalStateException("Empty response body")
extractJWK(didDocument)
} catch (e: Exception) {
// Handle errors, possibly log or rethrow as needed
println("Error processing DID: ${e.message}")
null
}
}
private fun extractJWK(didDocument: DIDDocument): ECKey? {
return try {
// Iterate through each verification method
for (method in didDocument.verificationMethods) {
try {
val publicKeyJwk = method.publicKeyJwk

// Check if 'crv' is 'P-256'
if (publicKeyJwk.crv == "P-256") {
// Convert the JSON JWK to a Nimbus JWK
val jwk = JWK.parse(
"""{
"kty": "${publicKeyJwk.kty}",
"crv": "${publicKeyJwk.crv}",
"x": "${publicKeyJwk.x}",
"y": "${publicKeyJwk.y}"
}"""
)
if (jwk is ECKey) {
return jwk
}
}
} catch (e: ParseException) {
// Handle JWK parsing exceptions
println("Error parsing JWK: ${e.message}")
}
}

// Return null if no matching JWK is found
null
} catch (e: Exception) {
// Handle any unexpected exceptions
println("Error processing DID document: ${e.message}")
null
}
}

/**
* Processes a JWK from a DID
Expand Down Expand Up @@ -100,8 +171,17 @@ class SignatureValidator {
val jwsObject = JWSObject.parse(jwt)

// Create a JWS verifier with the EC key
val verifier = ECDSAVerifier(jwk)
// val verifier = ECDSAVerifier(jwk)
// Get the algorithm from the JWS header
val algorithm = jwsObject.header.algorithm

// Create the appropriate verifier based on the algorithm
val verifier: JWSVerifier = when (algorithm) {
JWSAlgorithm.ES256 -> ECDSAVerifier(jwk.toECKey())
JWSAlgorithm.ES384 -> ECDSAVerifier(jwk.toECKey())
JWSAlgorithm.ES512 -> ECDSAVerifier(jwk.toECKey())
else -> throw JOSEException("Unsupported JWS algorithm $algorithm")
}
// Verify the JWS signature
return jwsObject.verify(verifier)
} catch (e: Exception) {
Expand All @@ -127,22 +207,27 @@ class SignatureValidator {
return null
}


/**
* Converts a JwkKey object to a JWK (JSON Web Key).
*
* @param jwkKey
* @return
* @param jwkKey The JwkKey object.
* @return The JWK object or null if jwkKey is null.
*/
private fun convertToJWK(jwkKey: JwkKey?): JWK? {
return jwkKey?.let {
ECKey.Builder(Curve.P_256, Base64URL.from(it.x), Base64URL.from(it.y))
val curve = when (it.crv) {
"P-256" -> Curve.P_256
"P-384" -> Curve.P_384
"P-521" -> Curve.P_521
else -> throw IllegalArgumentException("Unsupported curve: ${it.crv}")
}

ECKey.Builder(curve, Base64URL.from(it.x), Base64URL.from(it.y))
.keyID(it.kid)
.build()
}
}


/**
* Fetches a JwkKey object from a specified JWKS (JSON Web Key Set) URI.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import com.ewc.eudi_wallet_oidc_android.models.AuthorisationServerWellKnownConfi
import com.ewc.eudi_wallet_oidc_android.models.CredentialOffer
import com.ewc.eudi_wallet_oidc_android.models.CredentialRequest
import com.ewc.eudi_wallet_oidc_android.models.CredentialResponse
import com.ewc.eudi_wallet_oidc_android.models.DIDDocument
import com.ewc.eudi_wallet_oidc_android.models.IssuerWellKnownConfiguration
import com.ewc.eudi_wallet_oidc_android.models.TokenResponse
import okhttp3.ResponseBody
Expand Down Expand Up @@ -74,4 +75,7 @@ interface ApiService {
@Url url: String,
@FieldMap map: Map<String, String>
): Response<ResponseBody>

@GET
suspend fun ebsiDIDResolver(@Url url: String): Response<DIDDocument>
}

0 comments on commit 4b85c60

Please sign in to comment.