diff --git a/README.md b/README.md index 591fa9b5..146f5c59 100644 --- a/README.md +++ b/README.md @@ -49,6 +49,7 @@ import io.mosip.vercred.vcverifier.keyResolver.types.http.HttpsPublicKeyResolver | `mso_mdoc` | COSE (CBOR Object Signing and Encryption) | ES256 | Uses COSE_Sign1 | | `vc+sd-jwt` | X.509 Certificate (Currently, JWT VC Issuer Metadata is not supported) | PS256, RS256,ES256, EdDSA (Ed25519) | - | | `dc+sd-jwt` | X.509 Certificate (Currently, JWT VC Issuer Metadata is not supported) | PS256, RS256,ES256, EdDSA (Ed25519) | - | +| `cwt_vc` | COSE_Sign1 (CBOR Web Token – RFC 8392) | ES256, EdDSA (COSE alg based) | COSE_Sign1 | #### Project Structure diff --git a/doc/cwt-vc-verification-support.md b/doc/cwt-vc-verification-support.md new file mode 100644 index 00000000..46f4137a --- /dev/null +++ b/doc/cwt-vc-verification-support.md @@ -0,0 +1,121 @@ +# Support for IETF CWT Verifiable Credential (CWT-VC) + +This document provides a comprehensive overview of validating and verifying **CWT-based Verifiable Credentials (cwt-vc)** encoded using **CBOR Web Token (CWT)** and **COSE_Sign1**, as defined by IETF standards. + +--- + +## Public key resolution support + +- **HTTP / HTTPS Issuer** + - Retrieves issuer public key from `/.well-known/jwks.json` when `iss` is an HTTP(S) URL. + +--- + +## Steps Involved + +1. Added the enum value `CWT_VC("cwt-vc")` to `CredentialFormat`. +2. Created a new class `CwtVerifiableCredential` that implemented the `VerifiableCredential` interface. + - The `validate` method validated the credential structure and claims. + - The `verify` method verified the cryptographic signature. +3. Created a `CwtValidator` class to validate the credential structure and claims. +4. Created a `CwtVerifier` class to verify the credential signature. +5. Implemented validation checks for COSE, headers, claims, and numeric date fields. +6. Implemented signature verification using the issuer’s public key. +7. Registered `CwtVerifiableCredential` in `CredentialVerifierFactory`. + +--- + +## Sequence diagram – validate and verify `cwt-vc` credential + +```mermaid +sequenceDiagram + Wallet->>CredentialsVerifier: verify credential + CredentialsVerifier->>CredentialVerifierFactory: Create verifier + CredentialVerifierFactory->>CwtVerifiableCredential: Create instance + CredentialsVerifier->>CwtVerifiableCredential: Validate + CwtVerifiableCredential->>CwtValidator: Validate + CredentialsVerifier->>CwtVerifiableCredential: Verify + CwtVerifiableCredential->>CwtVerifier: Verify + CwtVerifier-->>CwtVerifiableCredential: Result + CwtVerifiableCredential-->>CredentialsVerifier: Result +``` + +### Sequence diagram - validation process + +```mermaid +sequenceDiagram + + CwtVerifiableCredential->>CwtValidator: Validate CWT Credential + CwtValidator->>CwtValidator: Validate input is non-empty hex string + CwtValidator->>CwtValidator: Decode hex to CBOR + CwtValidator->>CwtValidator: Validate COSE_Sign1 structure + Note over CwtValidator: COSE_Sign1 must be a CBOR array of size 4 + + CwtValidator->>CwtValidator: Decode protected header + CwtValidator->>CwtValidator: Validate protected header + Note over CwtValidator: alg must be present and must be an integer + + CwtValidator->>CwtValidator: Decode CWT claims + CwtValidator->>CwtValidator: Validate CWT claims structure + Note over CwtValidator: CWT payload must be a CBOR map + + CwtValidator->>CwtValidator: Validate numeric date claims + Note over CwtValidator: exp, nbf, iat are optional numeric dates + + alt exp present and expired + CwtValidator-->>CwtVerifiableCredential: Return Validation Result as False (VC expired) + else nbf present and in future + CwtValidator-->>CwtVerifiableCredential: Return Validation Result as False (Not Before violation) + else iat present and in future + CwtValidator-->>CwtVerifiableCredential: Return Validation Result as False (Invalid iat) + else Validation Success + CwtValidator-->>CwtVerifiableCredential: Return Validation Result as True + end +``` + +### Sequence diagram - verification process + +```mermaid +sequenceDiagram + + CwtVerifiableCredential->>CwtVerifier: Verify CWT Credential + CwtVerifier->>CwtVerifier: Decode hex to CBOR + CwtVerifier->>CwtVerifier: Validate CBOR Tag 61 (CWT) + alt Missing or invalid tag + CwtVerifier-->>CwtVerifiableCredential: Return Verification Result as False + else Valid CWT + CwtVerifier->>CwtVerifier: Extract COSE_Sign1 object + CwtVerifier->>CwtVerifier: Validate COSE_Sign1 structure + CwtVerifier->>CwtVerifier: Extract CWT claims + CwtVerifier->>CwtVerifier: Extract issuer (iss) + CwtVerifier->>CwtVerifier: Extract kid from protected or unprotected header + CwtVerifier->>PublicKeyResolverFactory: Resolve public key using issuer and kid + CwtVerifier->>CwtVerifier: Verify COSE_Sign1 signature + alt Signature Invalid + CwtVerifier-->>CwtVerifiableCredential: Return Verification Result as False + else Signature Valid + CwtVerifier-->>CwtVerifiableCredential: Return Verification Result as True + end + end + + +``` +--- + +## Key Validation Rules Summary + +### COSE Structure +- Must be a CBOR array of exactly 4 elements. + +### Protected Header +- `alg` MUST be present and an integer. + +### CWT Claims +- Payload MUST be a CBOR map. +- `iss`, `exp`, `nbf`, `iat` validated as per spec. + +### Cryptographic Verification +- CBOR tag `61` required. +- Signature verified using COSE_Sign1. + +--- diff --git a/vc-verifier/kotlin/gradle/libs.versions.toml b/vc-verifier/kotlin/gradle/libs.versions.toml index 2a65faa0..4a4a9f54 100644 --- a/vc-verifier/kotlin/gradle/libs.versions.toml +++ b/vc-verifier/kotlin/gradle/libs.versions.toml @@ -27,7 +27,7 @@ annotationJvm = "1.9.1" cbor = "0.9" identity = "20231002" authleteSdJwt = "1.5" -coseLibrary = "2.0.0" +cborLibrary = "4.5.6" authleteCbor = "1.19" [libraries] @@ -64,7 +64,7 @@ mockWebServer = { group = "com.squareup.okhttp3", name = "mockwebserver", versio annotation-jvm = { group = "androidx.annotation", name = "annotation-jvm", version.ref = "annotationJvm" } cbor = { group = "co.nstant.in", name = "cbor", version.ref = "cbor" } identity = { group = "com.android.identity", name = "identity-credential", version.ref = "identity" } -cose-lib = { group = "se.digg.cose", name = "cose-lib", version.ref = "coseLibrary" } +upokecenter-cbor = { group = "com.upokecenter", name = "cbor", version.ref = "cborLibrary" } authlete-cbor = { group = "com.authlete", name = "cbor", version.ref = "authleteCbor" } [plugins] diff --git a/vc-verifier/kotlin/vcverifier/build.gradle.kts b/vc-verifier/kotlin/vcverifier/build.gradle.kts index bd273ade..bbe9c1a9 100644 --- a/vc-verifier/kotlin/vcverifier/build.gradle.kts +++ b/vc-verifier/kotlin/vcverifier/build.gradle.kts @@ -68,7 +68,7 @@ dependencies { implementation(libs.annotation.jvm) implementation(libs.authelete.sd.jwt) implementation(libs.threetenbp) - implementation(libs.cose.lib) + implementation(libs.upokecenter.cbor) implementation(libs.authlete.cbor) testImplementation(libs.mockk) diff --git a/vc-verifier/kotlin/vcverifier/src/main/java/io/mosip/vercred/vcverifier/keyResolver/types/jwks/JwksPublicKeyResolver.kt b/vc-verifier/kotlin/vcverifier/src/main/java/io/mosip/vercred/vcverifier/keyResolver/types/jwks/JwksPublicKeyResolver.kt index 6623baba..58cc9813 100644 --- a/vc-verifier/kotlin/vcverifier/src/main/java/io/mosip/vercred/vcverifier/keyResolver/types/jwks/JwksPublicKeyResolver.kt +++ b/vc-verifier/kotlin/vcverifier/src/main/java/io/mosip/vercred/vcverifier/keyResolver/types/jwks/JwksPublicKeyResolver.kt @@ -30,8 +30,8 @@ class JwksPublicKeyResolver : PublicKeyResolver { return getPublicKeyFromJWK(jwk, kty) } catch (e: Exception) { - logger.severe("Error fetching public key string $e") - throw PublicKeyNotFoundException("Public key string not found") + throw if (e is PublicKeyNotFoundException) e + else PublicKeyNotFoundException("Failed to resolve JWKS public key: ${e.message}").apply { initCause(e) } } }