Skip to content

[INJIVER-1369] - Add verification and validation for cwt formet VC with new library#216

Merged
mayuradesh merged 12 commits intoinji:developfrom
tw-mosip:injiver-1369-new
Jan 16, 2026
Merged

[INJIVER-1369] - Add verification and validation for cwt formet VC with new library#216
mayuradesh merged 12 commits intoinji:developfrom
tw-mosip:injiver-1369-new

Conversation

@jaswanthkumartw
Copy link

@jaswanthkumartw jaswanthkumartw commented Jan 6, 2026

Summary by CodeRabbit

  • New Features

    • Support for CWT (CBOR Web Token) credentials with COSE signature verification, plus validator and verifier flows.
    • JWKS-based HTTP public key resolution with optional key ID.
    • Utilities for hex/CBOR handling and numeric date validation.
  • Tests

    • Unit tests covering many CWT validation scenarios; verifier test scaffold added.
  • Chores

    • Packaging exclude added for a META-INF manifest; COSE library reference added.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link

coderabbitai bot commented Jan 6, 2026

Walkthrough

Adds CWT (CBOR Web Token) support: new CwtValidator and CwtVerifier, a CwtVerifiableCredential type and factory branch, JWKS-based public key resolution with optional keyId, JWK-to-PublicKey utilities, hex/CBOR helpers, unit tests, and Gradle dependency/config updates.

Changes

Cohort / File(s) Summary
Build / Dependency config
vc-verifier/kotlin/example/build.gradle.kts, vc-verifier/kotlin/gradle/libs.versions.toml, vc-verifier/kotlin/vcverifier/build.gradle.kts
Added se.digg.cose:cose-lib:2.0.0; excluded BouncyCastle artifacts via resolutionStrategy; added packaging exclusion for META-INF/versions/9/OSGI-INF/MANIFEST.MF.
Credential format & constants
vc-verifier/kotlin/vcverifier/src/main/java/.../CredentialFormat.kt, .../CredentialValidatorConstants.kt
Added CWT_VC enum constant and two validation error message constants for empty/invalid hex CWT inputs.
CWT validator & tests
vc-verifier/kotlin/vcverifier/src/main/java/.../validator/CwtValidator.kt, .../validator/CwtValidatorTest.kt
New CwtValidator implements hex decoding, COSE_Sign1 structural checks, protected header/alg validation, CBOR claims parsing, and exp/nbf/iat numeric-date checks; comprehensive unit tests added.
CWT verifier & tests
vc-verifier/kotlin/vcverifier/src/main/java/.../verifier/CwtVerifier.kt, .../verifier/CwtVerifierTest.kt
New CwtVerifier decodes COSE_Sign1, extracts claims/issuer and optional kid, resolves public key (via PublicKeyResolverFactory), and verifies signature; test scaffold added.
VerifiableCredential integration
vc-verifier/kotlin/vcverifier/src/main/java/.../types/CwtVerifiableCredential.kt, .../CredentialVerifierFactory.kt
New CwtVerifiableCredential delegates to validator/verifier and returns empty status map; factory extended to return this type for CWT_VC.
Key resolution & JWK utilities
vc-verifier/kotlin/vcverifier/src/main/java/.../keyResolver/PublicKeyResolverFactory.kt, .../keyResolver/Utils.kt, .../keyResolver/types/jwks/JwksPublicKeyResolver.kt
PublicKeyResolverFactory.get now accepts optional keyId; detects JWKS endpoints (jwks.json) and delegates to new JwksPublicKeyResolver which fetches JWKS, selects key by kid, and uses expanded getPublicKeyFromJWK(Map<String,Any>, keyType) to build RSA/EC/OKP keys.
Utilities
vc-verifier/kotlin/vcverifier/src/main/java/.../utils/Util.kt
Added hexToBytes and validateNumericDate helpers to support hex decoding and numeric-date extraction/validation from CBOR claims.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant CredentialVerifierFactory
    participant CwtVerifiableCredential
    participant CwtValidator
    participant Util

    Client->>CredentialVerifierFactory: get(CWT_VC)
    CredentialVerifierFactory->>CwtVerifiableCredential: instantiate
    CredentialVerifierFactory-->>Client: CwtVerifiableCredential

    Client->>CwtVerifiableCredential: validate(credential)
    CwtVerifiableCredential->>CwtValidator: validate(credential)
    CwtValidator->>Util: hexToBytes(credential)
    Util-->>CwtValidator: bytes
    CwtValidator->>CwtValidator: decode COSE_Sign1 & parse CBOR claims
    CwtValidator->>Util: validateNumericDate(claims, exp/nbf/iat)
    CwtValidator-->>CwtVerifiableCredential: ValidationStatus
    CwtVerifiableCredential-->>Client: ValidationStatus
Loading
sequenceDiagram
    participant Client
    participant CwtVerifiableCredential
    participant CwtVerifier
    participant Util
    participant PublicKeyResolverFactory
    participant JwksPublicKeyResolver

    Client->>CwtVerifiableCredential: verify(credential)
    CwtVerifiableCredential->>CwtVerifier: verify(credential)
    CwtVerifier->>Util: hexToBytes(credential)
    Util-->>CwtVerifier: bytes
    CwtVerifier->>CwtVerifier: decode COSE_Sign1 & extract claims, iss, kid
    CwtVerifier->>PublicKeyResolverFactory: get(issuerURI, keyId)
    PublicKeyResolverFactory->>JwksPublicKeyResolver: resolve(jwksUrl, keyId)
    JwksPublicKeyResolver->>JwksPublicKeyResolver: fetch JWKS, select key by kid
    JwksPublicKeyResolver-->>PublicKeyResolverFactory: PublicKey
    PublicKeyResolverFactory-->>CwtVerifier: PublicKey
    CwtVerifier->>CwtVerifier: verifySignature(coseBytes, PublicKey)
    CwtVerifier-->>CwtVerifiableCredential: boolean
    CwtVerifiableCredential-->>Client: boolean
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • swatigoel
  • abhip2565

Poem

🐰
I hopped through CBOR, nibbling hex dust,
Found COSE signatures I could trust.
JWKS keys glinting in the dew,
Dates checked true, signatures too.
Hooray — CWTs verified anew! ✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title mentions 'cwt formet VC' but contains a typo ('formet' instead of 'format'), and the main changes include supporting CWT credentials, JWKS-based key resolution, and related infrastructure, which the title partially captures.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

🧹 Recent nitpick comments
vc-verifier/kotlin/vcverifier/src/test/java/io/mosip/vercred/vcverifier/keyResolver/PublicKeyResolverFactoryTest.kt (1)

102-125: Consider strengthening test assertions.

The test verifies that resolution doesn't throw, which confirms a key was found (based on the factory's behavior of throwing PublicKeyNotFoundException on failure). However, it doesn't verify:

  • The actual public key content matches expectations
  • The correct resolver (JwksPublicKeyResolver) was used

Other tests in this file (e.g., did_key, did_jwk) use assertPublicKey to verify the resolved key.

♻️ Suggested improvement
     `@Test`
     fun `should use JwksPublicKeyResolver when keyId is provided`() {
         val jwksUri = URI("https://mock-server.com/.well-known/jwks.json")
         val keyId = "test-key-id"
+        val expectedEncodedPublicKey =
+            "[48, 42, 48, 5, 6, 3, 43, 101, 112, 3, 33, 0, -14, 15, 93, -4, -64, 116, -119, 77, -89, -102, 6, -1, -12, -2, 3, 127, 68, -47, 66, 110, 81, 37, 57, -102, -120, 73, 54, 30, 70, 114, -26, -111]"

         val mockResponse = mapOf(
             "keys" to listOf(
                 mapOf(
                     "kty" to "OKP",
                     "crv" to "Ed25519",
                     "x" to "8g9d_MB0iU2nmgb_9P4Df0TRQm5RJTmaiEk2HkZy5pE",
                     "kid" to keyId
                 )
             )
         )

         every {
             sendHTTPRequest(jwksUri.toString(), HttpMethod.GET, any(), any())
         } returns mockResponse

-        assertDoesNotThrow {
-            factory.get(jwksUri, keyId)
-        }
+        val publicKey = factory.get(jwksUri, keyId)
+
+        assertPublicKey(publicKey, expectedEncodedPublicKey)
     }

📜 Recent review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c6574d7 and 1c061d2.

📒 Files selected for processing (1)
  • vc-verifier/kotlin/vcverifier/src/test/java/io/mosip/vercred/vcverifier/keyResolver/PublicKeyResolverFactoryTest.kt
🧰 Additional context used
🧠 Learnings (3)
📓 Common learnings
Learnt from: jaswanthkumartw
Repo: inji/vc-verifier PR: 216
File: vc-verifier/kotlin/vcverifier/src/main/java/io/mosip/vercred/vcverifier/credentialverifier/verifier/CwtVerifier.kt:22-26
Timestamp: 2026-01-09T06:10:25.708Z
Learning: In the CWT verification flow for the CwtVerifier and CwtValidator classes, validation is performed before verification. CwtValidator.validate() checks structure and claims first, then CwtVerifier.verify() performs signature verification. The verifier does not need to duplicate detailed structure validation since it's already handled by the validator.
📚 Learning: 2026-01-07T10:57:37.322Z
Learnt from: jaswanthkumartw
Repo: inji/vc-verifier PR: 216
File: vc-verifier/kotlin/vcverifier/src/main/java/io/mosip/vercred/vcverifier/credentialverifier/verifier/CwtVerifier.kt:95-123
Timestamp: 2026-01-07T10:57:37.322Z
Learning: In the CwtVerifier class, the `PublicKeyResolverFactory().get(issuer)` method throws `PublicKeyNotFoundException` when a key cannot be resolved, rather than returning null.

Applied to files:

  • vc-verifier/kotlin/vcverifier/src/test/java/io/mosip/vercred/vcverifier/keyResolver/PublicKeyResolverFactoryTest.kt
📚 Learning: 2026-01-09T05:57:56.476Z
Learnt from: jaswanthkumartw
Repo: inji/vc-verifier PR: 216
File: vc-verifier/kotlin/vcverifier/src/main/java/io/mosip/vercred/vcverifier/keyResolver/Utils.kt:79-85
Timestamp: 2026-01-09T05:57:56.476Z
Learning: In the file vc-verifier/kotlin/vcverifier/src/main/java/io/mosip/vercred/vcverifier/keyResolver/Utils.kt, the constant JWK_KEY_TYPE_EC is defined as "EC", so when this constant is used in pattern matching, it already covers the raw "EC" string value.

Applied to files:

  • vc-verifier/kotlin/vcverifier/src/test/java/io/mosip/vercred/vcverifier/keyResolver/PublicKeyResolverFactoryTest.kt
🔇 Additional comments (1)
vc-verifier/kotlin/vcverifier/src/test/java/io/mosip/vercred/vcverifier/keyResolver/PublicKeyResolverFactoryTest.kt (1)

78-78: LGTM!

Correct fix - the HttpsPublicKeyResolver test now appropriately uses a DID document endpoint (did.json) instead of a JWKS endpoint. This properly differentiates the HTTPS/DID resolution from the new JWKS-based resolution tested separately.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@jaswanthkumartw jaswanthkumartw changed the title [INJIVER-1369] - Add verification and validation for cwt formet VC [INJIVER-1369] - Add verification and validation for cwt formet VC with new library Jan 6, 2026
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 8

🤖 Fix all issues with AI Agents
In @vc-verifier/kotlin/gradle/libs.versions.toml:
- Around line 23-24: The jackson dependency versions are inconsistent:
libs.versions.toml sets jacksonAnnotations and jacksonModuleKotlin to 2.17.2
while vcverifier/build.gradle.kts forces jackson-core:2.14.0; update the forced
dependency in build.gradle.kts (the jackson-core version pin around the
dependency declaration) to 2.17.2 to match
jacksonAnnotations/jacksonModuleKotlin, ensure jackson-module-kotlin and
jackson-core are aligned, then run and validate Jackson-dependent code (Kotlin
serialization/deserialization, polymorphic JsonTypeInfo, enum handling and any
code that may be affected by StreamRead/Write and coercion changes) to address
breaking changes introduced between 2.14.0 and 2.17.2.

In @vc-verifier/kotlin/vcverifier/build.gradle.kts:
- Around line 76-81: The global exclusions in the configurations.all block are
removing the same BouncyCastle artifact you explicitly add via
implementation(libs.bouncyCastle) and conflict with other configurations.all
blocks; replace the global exclusions with per-dependency excludes on the
dependencies that bring in the unwanted bcprov/bcpkix/bcutil transitive
artifacts (e.g., update implementation(libs.cose.lib) and any other dependency
entries that transitively pull BouncyCastle to add
exclude(group="org.bouncycastle", module="bcprov-jdk15on") /
exclude(group="org.bouncycastle", module="bcpkix-jdk15on") /
exclude(group="org.bouncycastle", module="bcutil-jdk15on") so you can keep
implementation(libs.bouncyCastle) while removing only the conflicting transitive
versions and remove the contradictory configurations.all exclusions.
- Line 68: The dependency declaration implementation(libs.cose.lib) references
se.digg.cose:cose-lib version 2.0.0 which is not on Maven Central; either
confirm the required repository (e.g., Danubetech or JitPack) is added to the
Gradle repositories block or change the version to one that is published (update
the libs.versions or the explicit version in build.gradle.kts). Locate the
implementation(libs.cose.lib) entry and ensure the repository that hosts
se.digg.cose:cose-lib is configured (or replace libs.cose.lib with a resolvable
version) so the project can resolve the dependency during build.

In
@vc-verifier/kotlin/vcverifier/src/main/java/io/mosip/vercred/vcverifier/credentialverifier/verifier/CwtVerifier.kt:
- Line 3: The import android.net.Uri in CwtVerifier.kt is unused and
Android-specific; remove the unused import statement from the top of the file
(the line importing android.net.Uri) so the CwtVerifier class and its functions
compile cleanly in non-Android JVM environments.
- Around line 19-26: Remove the duplicate local function hexToBytes from
CwtVerifier and replace its usages with the existing utility: call
Util.hexToBytes(...) (or add the single-function import
io.mosip.vercred.vcverifier.utils.Util.hexToBytes and call hexToBytes(...));
ensure all references inside CwtVerifier that previously called the local
hexToBytes now call the util method and delete the redundant function
definition.
- Around line 139-153: In verify(credential) fix missing error handling around
PublicKeyResolverFactory().get(issuer) by wrapping the call in a try-catch and
returning false if it throws, and also check the returned publicKey for null and
return false before calling verifySignature(coseBytes, publicKey); additionally
remove the unnecessary semicolons after statements (e.g., the semicolons after
decodeCose(...), isValidCoseStructure(...), CBORObject.DecodeFromBytes(...), and
isValidCwtStructure(...)) so the method consistently follows the defensive
return-false pattern.
- Around line 77-117: Dead-code: fetchPublicKey and extractKid implement JWKS
resolution but are never used; either remove them or wire them into the
verification flow. To fix, either delete the unused methods fetchPublicKey(...)
and extractKid(...) (clean up imports) or modify the verify() path that
currently uses PublicKeyResolverFactory to call fetchPublicKey(coseObj,
issuerMetadataJson) (or a new adapter method) and fall back to
PublicKeyResolverFactory if fetchPublicKey returns null; ensure the verify()
caller supplies issuerMetadataJson (parse issuer metadata where needed) and
handle null PublicKey results consistently with existing error handling.

In
@vc-verifier/kotlin/vcverifier/src/test/java/io/mosip/vercred/vcverifier/credentialverifier/verifier/CwtVerifierTest.kt:
- Around line 22-92: The CwtVerifierTest's entire test method `should verify
valid CWT` is commented out leaving CwtVerifier untested; either re-enable and
fix the test, remove it with a TODO and issue, or document why it's disabled. To
fix: restore the test in class `CwtVerifierTest` (uncomment the `should verify
valid CWT` method), ensure necessary imports and test annotations are present,
fix any runtime issues by mocking `Util.httpGet` correctly (the current mock
targets `Util.httpGet(any())` and must return the `issuerMetadataJson` and
`jwksJson` for `"$issuer/.well-known/openid-credential-issuer"` and
`"$issuer/jwks"`), and verify the COSE creation code (references:
`Sign1COSEObject`, `COSEKey`, `ECKey.Builder`, `KeyPairGenerator`) compiles and
the asserted call `CwtVerifier().verify(coseHex)` returns true; alternatively,
if intentionally disabled remove the commented block in `CwtVerifierTest` and
add a single-line TODO with a tracking issue referencing `CwtVerifier` and
`should verify valid CWT`.
🧹 Nitpick comments (11)
vc-verifier/kotlin/example/build.gradle.kts (1)

51-51: Consider documenting the reason for this exclusion.

The exclusion of META-INF/versions/9/OSGI-INF/MANIFEST.MF suggests a duplicate resource conflict, likely introduced by the new COSE library or updated dependencies. While this exclusion is valid, consider adding a comment explaining which dependency causes the conflict to help future maintainers.

🔎 Suggested improvement
             excludes += "/META-INF/{AL2.0,LGPL2.1}"
+            // Exclude duplicate OSGI manifest from cose-lib or transitive dependencies
             excludes += "META-INF/versions/9/OSGI-INF/MANIFEST.MF"
vc-verifier/kotlin/vcverifier/src/main/java/io/mosip/vercred/vcverifier/constants/CredentialValidatorConstants.kt (1)

55-58: Fix formatting and consider adding error codes for consistency.

Formatting issues:

  • Missing space after = operator on lines 55 and 57 (inconsistent with the rest of the file)
  • Unnecessary blank lines at lines 56 and 58 (breaks the file's consistent spacing pattern)

Optional improvement:
Similar to ERROR_MESSAGE_EMPTY_VC_JSON having a corresponding ERROR_CODE_EMPTY_VC_JSON (lines 36, 42), consider adding error codes for the new CWT constants (e.g., ERROR_CODE_EMPTY_VC_CWT and ERROR_CODE_INVALID_HEX_VC_CWT) to maintain consistency across validation error handling.

🔎 Proposed formatting fix
-    const val ERROR_MESSAGE_EMPTY_VC_CWT="${VALIDATION_ERROR}Input VC CWT string is null or empty."
-
-    const val ERROR_MESSAGE_INVALID_HEX_VC_CWT="${VALIDATION_ERROR}Invalid hexadecimal format"
-
+    const val ERROR_MESSAGE_EMPTY_VC_CWT = "${VALIDATION_ERROR}Input VC CWT string is null or empty."
+    const val ERROR_MESSAGE_INVALID_HEX_VC_CWT = "${VALIDATION_ERROR}Invalid hexadecimal format"
vc-verifier/kotlin/vcverifier/src/main/java/io/mosip/vercred/vcverifier/credentialverifier/types/CwtVerifiableCredential.kt (1)

14-20: Remove unnecessary semicolons and consider documenting the no-op checkStatus.

Kotlin doesn't require semicolons. Additionally, consider adding a TODO comment to checkStatus if status checking for CWT credentials is planned for future implementation, or document why it's intentionally a no-op.

🔎 Proposed fix
     override fun verify(credential: String): Boolean {
-        return CwtVerifier().verify(credential);
+        return CwtVerifier().verify(credential)
     }

+    // TODO: Implement status checking for CWT credentials if required
     override fun checkStatus(credential: String, statusPurposes: List<String>?): Map<String, CredentialStatusResult> {
-        return emptyMap();
+        return emptyMap()
     }
vc-verifier/kotlin/vcverifier/src/test/java/io/mosip/vercred/vcverifier/credentialverifier/validator/CwtValidatorTest.kt (2)

13-15: Remove the empty setup() method.

The empty @BeforeEach method adds no value and was flagged by static analysis.

🔎 Proposed fix
-    @BeforeEach
-    fun setup() {
-    }
-
     private val validator = CwtValidator()

34-46: Consider consistent test naming convention.

Test method names use inconsistent prefixes (validate - vs test -). Consider standardizing to a single pattern for better readability and organization.

vc-verifier/kotlin/vcverifier/src/main/java/io/mosip/vercred/vcverifier/utils/Util.kt (3)

36-36: Consider making restTemplate private.

Exposing restTemplate as a public val leaks the internal HTTP implementation. If external access is needed, consider providing a factory method or making it internal/private.

🔎 Proposed fix
-    val restTemplate = RestTemplate()
+    private val restTemplate = RestTemplate()

152-159: hexToBytes throws unchecked exception on invalid hex characters.

If the input contains non-hex characters (e.g., "ZZ"), toInt(16) throws NumberFormatException. While CwtValidator validates hex before calling this, other callers may not. Consider adding character validation or documenting the precondition.

🔎 Proposed fix
     fun hexToBytes(hex: String): ByteArray {
         val cleanHex = hex.replace("\\s".toRegex(), "")
         require(cleanHex.length % 2 == 0) { "Invalid hex length" }
+        require(cleanHex.matches("^[0-9a-fA-F]*$".toRegex())) { "Invalid hex characters" }

         return ByteArray(cleanHex.length / 2) { i ->
             cleanHex.substring(i * 2, i * 2 + 2).toInt(16).toByte()
         }
     }

177-183: Consider logging HTTP failures for debuggability.

Silently returning null on any exception (network errors, timeouts, HTTP 4xx/5xx) can make debugging difficult. Consider logging at debug/trace level.

vc-verifier/kotlin/vcverifier/src/main/java/io/mosip/vercred/vcverifier/credentialverifier/validator/CwtValidator.kt (2)

130-135: Use isIntegral() instead of isNumber for stricter alg validation.

Per COSE specification, alg must be an integer, not a floating-point number. isNumber returns true for floats as well. Consider using isIntegral() for stricter validation.

🔎 Proposed fix
-        if (!protectedHeader[ALG].isNumber) {
+        if (!protectedHeader[ALG].isIntegral) {
             throw ValidationException(
                 ERROR_INVALID_FIELD + "alg must be an integer",
                 ERROR_CODE_INVALID + "ALG"
             )
         }

204-207: Inconsistency with Util.hexToBytes whitespace handling.

isValidHex rejects any whitespace, but Util.hexToBytes strips whitespace before processing. Consider aligning behavior - either allow whitespace in validation (strip before regex check) or document that whitespace is not permitted.

🔎 Proposed fix to align with Util.hexToBytes
     private fun isValidHex(credential: String): Boolean {
+        val cleanCredential = credential.replace("\\s".toRegex(), "")
         val hexRegex = "^[0-9a-fA-F]+$".toRegex()
-        return credential.matches(hexRegex)
+        return cleanCredential.isNotEmpty() && cleanCredential.matches(hexRegex)
     }
vc-verifier/kotlin/vcverifier/src/main/java/io/mosip/vercred/vcverifier/credentialverifier/verifier/CwtVerifier.kt (1)

14-14: Remove unused import java.net.URL.

🔎 Proposed fix
-import java.net.URL
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between bf09af6 and ba7a8a2.

📒 Files selected for processing (13)
  • vc-verifier/kotlin/example/build.gradle.kts
  • vc-verifier/kotlin/example/src/main/java/io/mosip/vccred/example/MainActivity.kt
  • vc-verifier/kotlin/gradle/libs.versions.toml
  • vc-verifier/kotlin/vcverifier/build.gradle.kts
  • vc-verifier/kotlin/vcverifier/src/main/java/io/mosip/vercred/vcverifier/constants/CredentialFormat.kt
  • vc-verifier/kotlin/vcverifier/src/main/java/io/mosip/vercred/vcverifier/constants/CredentialValidatorConstants.kt
  • vc-verifier/kotlin/vcverifier/src/main/java/io/mosip/vercred/vcverifier/credentialverifier/CredentialVerifierFactory.kt
  • vc-verifier/kotlin/vcverifier/src/main/java/io/mosip/vercred/vcverifier/credentialverifier/types/CwtVerifiableCredential.kt
  • vc-verifier/kotlin/vcverifier/src/main/java/io/mosip/vercred/vcverifier/credentialverifier/validator/CwtValidator.kt
  • vc-verifier/kotlin/vcverifier/src/main/java/io/mosip/vercred/vcverifier/credentialverifier/verifier/CwtVerifier.kt
  • vc-verifier/kotlin/vcverifier/src/main/java/io/mosip/vercred/vcverifier/utils/Util.kt
  • vc-verifier/kotlin/vcverifier/src/test/java/io/mosip/vercred/vcverifier/credentialverifier/validator/CwtValidatorTest.kt
  • vc-verifier/kotlin/vcverifier/src/test/java/io/mosip/vercred/vcverifier/credentialverifier/verifier/CwtVerifierTest.kt
👮 Files not reviewed due to content moderation or server errors (1)
  • vc-verifier/kotlin/example/src/main/java/io/mosip/vccred/example/MainActivity.kt
🧰 Additional context used
🧬 Code graph analysis (2)
vc-verifier/kotlin/vcverifier/src/main/java/io/mosip/vercred/vcverifier/credentialverifier/validator/CwtValidator.kt (1)
vc-verifier/kotlin/vcverifier/src/main/java/io/mosip/vercred/vcverifier/utils/Util.kt (2)
  • validateNumericDate (161-175)
  • hexToBytes (152-159)
vc-verifier/kotlin/vcverifier/src/main/java/io/mosip/vercred/vcverifier/credentialverifier/verifier/CwtVerifier.kt (1)
vc-verifier/kotlin/vcverifier/src/main/java/io/mosip/vercred/vcverifier/utils/Util.kt (2)
  • hexToBytes (152-159)
  • httpGet (177-183)
🪛 detekt (1.23.8)
vc-verifier/kotlin/vcverifier/src/test/java/io/mosip/vercred/vcverifier/credentialverifier/validator/CwtValidatorTest.kt

[warning] 14-15: This empty block of code can be removed.

(detekt.empty-blocks.EmptyFunctionBlock)

🔇 Additional comments (4)
vc-verifier/kotlin/gradle/libs.versions.toml (1)

30-30: LGTM! New COSE library properly configured.

The new coseLibrary version reference and cose-lib library alias are correctly defined and follow the existing conventions in the version catalog.

Also applies to: 66-66

vc-verifier/kotlin/vcverifier/src/main/java/io/mosip/vercred/vcverifier/credentialverifier/CredentialVerifierFactory.kt (1)

4-4: LGTM! Factory properly extended for CWT format.

The addition of CwtVerifiableCredential to the factory follows the existing pattern and correctly handles the new CredentialFormat.CWT_VC format.

Also applies to: 16-16

vc-verifier/kotlin/vcverifier/src/main/java/io/mosip/vercred/vcverifier/constants/CredentialFormat.kt (1)

4-4: LGTM! New credential format added correctly.

The CWT_VC enum constant follows the existing naming pattern and integrates seamlessly with the fromValue() method.

vc-verifier/kotlin/vcverifier/src/main/java/io/mosip/vercred/vcverifier/credentialverifier/validator/CwtValidator.kt (1)

24-59: LGTM - Clean validation flow with proper error handling.

The validation structure is well-organized with clear separation between structure validation, header validation, and date validation. Exception-based control flow provides consistent error reporting.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Fix all issues with AI agents
In
@vc-verifier/kotlin/vcverifier/src/main/java/io/mosip/vercred/vcverifier/credentialverifier/verifier/CwtVerifier.kt:
- Around line 19-26: Remove the duplicate hexToBytes function from CwtVerifier
and replace any internal calls to hexToBytes with Util.hexToBytes; specifically,
delete the fun hexToBytes(...) definition in CwtVerifier.kt, add the appropriate
import or fully qualify calls to Util.hexToBytes wherever this local function
was used, and ensure compilation by resolving any missing imports or references
to the Util class.
- Around line 105-129: The call PublicKeyResolverFactory().get(issuer) can
return null; before calling verifySignature(coseBytes, publicKey) add a null
check and throw PublicKeyNotFoundException (or a suitable exception) with a
clear message including the issuer when publicKey is null; ensure the existing
catch block still propagates PublicKeyNotFoundException so callers see the
specific failure rather than a NullPointerException.

In
@vc-verifier/kotlin/vcverifier/src/main/java/io/mosip/vercred/vcverifier/keyResolver/types/did/DidWebPublicKeyResolver.kt:
- Around line 40-42: The current use of substring matching in
verificationMethods.find ((it[ID] as? String)?.contains(verificationMethodId) ==
true) can resolve the wrong key; change the matching to exact equality: compare
the ID string exactly to verificationMethodId (or, if you must support
fragment/URI variants, perform a deterministic comparison such as URI
normalization and exact equality or use suffix/fragment-only matching like
endsWith after validating the scheme) so that verificationMethod selection via
verificationMethodId is unambiguous and not order-dependent.
🧹 Nitpick comments (2)
vc-verifier/kotlin/vcverifier/src/main/java/io/mosip/vercred/vcverifier/credentialverifier/verifier/CwtVerifier.kt (2)

58-75: Remove unused extractKid method.

This method extracts the Key ID from the COSE header but is never called. The verify() method resolves the public key using only the issuer URI via PublicKeyResolverFactory, bypassing any KID-based resolution.

Either remove this dead code or integrate KID-based key resolution into the verification flow if needed.


28-35: Log swallowed exception in decodeCose.

The caught exception is silently swallowed when CBOR decoding fails. This makes debugging difficult when malformed credentials are provided.

📝 Add logging
    private fun decodeCose(cwtBytes: ByteArray): CBORObject? {
        return try {
            CBORObject.DecodeFromBytes(cwtBytes)
        } catch (e: Exception) {
-
+            logger.warning("Failed to decode COSE object: ${e.message}")
            null
        }
    }

Based on static analysis hints.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ba7a8a2 and 5392095.

📒 Files selected for processing (5)
  • vc-verifier/kotlin/example/src/main/java/io/mosip/vccred/example/MainActivity.kt
  • vc-verifier/kotlin/vcverifier/src/main/java/io/mosip/vercred/vcverifier/credentialverifier/verifier/CwtVerifier.kt
  • vc-verifier/kotlin/vcverifier/src/main/java/io/mosip/vercred/vcverifier/keyResolver/types/did/DidWebPublicKeyResolver.kt
  • vc-verifier/kotlin/vcverifier/src/main/java/io/mosip/vercred/vcverifier/utils/Util.kt
  • vc-verifier/kotlin/vcverifier/src/test/java/io/mosip/vercred/vcverifier/credentialverifier/verifier/CwtVerifierTest.kt
🚧 Files skipped from review as they are similar to previous changes (1)
  • vc-verifier/kotlin/vcverifier/src/main/java/io/mosip/vercred/vcverifier/utils/Util.kt
👮 Files not reviewed due to content moderation or server errors (1)
  • vc-verifier/kotlin/example/src/main/java/io/mosip/vccred/example/MainActivity.kt
🧰 Additional context used
🧬 Code graph analysis (1)
vc-verifier/kotlin/vcverifier/src/main/java/io/mosip/vercred/vcverifier/credentialverifier/verifier/CwtVerifier.kt (1)
vc-verifier/kotlin/vcverifier/src/main/java/io/mosip/vercred/vcverifier/utils/Util.kt (1)
  • hexToBytes (149-156)
🪛 detekt (1.23.8)
vc-verifier/kotlin/vcverifier/src/main/java/io/mosip/vercred/vcverifier/credentialverifier/verifier/CwtVerifier.kt

[warning] 70-70: The caught exception is swallowed. The original exception could be lost.

(detekt.exceptions.SwallowedException)


[warning] 87-87: The caught exception is swallowed. The original exception could be lost.

(detekt.exceptions.SwallowedException)

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In
@vc-verifier/kotlin/vcverifier/src/main/java/io/mosip/vercred/vcverifier/credentialverifier/verifier/CwtVerifier.kt:
- Around line 29-36: Remove the unused helper method decodeCose: it's never
invoked (verify() calls CBORObject.DecodeFromBytes() directly), so delete the
private fun decodeCose(cwtBytes: ByteArray): CBORObject? declaration and its
body to eliminate dead code; if you prefer keeping an abstraction, refactor
verify() to call decodeCose instead, but do not leave an unused decodeCose
method in the class.
- Around line 64-81: The extractKid(CBORObject) method is unused; either remove
it or wire it into the verification path: update verify() in CwtVerifier to call
extractKid(coseObj) and, when a KID is returned, use it to select the matching
key from the resolved JWKS (prefer the KID-matching JWK when constructing the
public key), falling back to issuer-based resolution if no KID or no match;
ensure null-safe handling and remove extractKid if you choose deletion, along
with any now-unused imports.
🧹 Nitpick comments (1)
vc-verifier/kotlin/vcverifier/src/main/java/io/mosip/vercred/vcverifier/credentialverifier/verifier/CwtVerifier.kt (1)

20-27: Remove duplicate hexToBytes - use Util.hexToBytes instead.

This function duplicates Util.hexToBytes from the utilities package. Maintaining duplicate code violates DRY and increases maintenance burden.

♻️ Proposed fix

Import and use the existing utility:

+import io.mosip.vercred.vcverifier.utils.Util
+
 class CwtVerifier {
 
    private val logger = Logger.getLogger(CwtVerifier::class.java.name)
-    fun hexToBytes(hex: String): ByteArray {
-        val cleanHex = hex.replace("\\s".toRegex(), "")
-        require(cleanHex.length % 2 == 0) { "Invalid hex length" }
-
-        return ByteArray(cleanHex.length / 2) { i ->
-            cleanHex.substring(i * 2, i * 2 + 2).toInt(16).toByte()
-        }
-    }

Then replace the call at line 115:

-            val coseBytes = hexToBytes(credential)
+            val coseBytes = Util.hexToBytes(credential)
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5392095 and 724202a.

📒 Files selected for processing (2)
  • vc-verifier/kotlin/vcverifier/src/main/java/io/mosip/vercred/vcverifier/credentialverifier/verifier/CwtVerifier.kt
  • vc-verifier/kotlin/vcverifier/src/test/java/io/mosip/vercred/vcverifier/credentialverifier/verifier/CwtVerifierTest.kt
🚧 Files skipped from review as they are similar to previous changes (1)
  • vc-verifier/kotlin/vcverifier/src/test/java/io/mosip/vercred/vcverifier/credentialverifier/verifier/CwtVerifierTest.kt
🧰 Additional context used
🧬 Code graph analysis (1)
vc-verifier/kotlin/vcverifier/src/main/java/io/mosip/vercred/vcverifier/credentialverifier/verifier/CwtVerifier.kt (1)
vc-verifier/kotlin/vcverifier/src/main/java/io/mosip/vercred/vcverifier/utils/Util.kt (1)
  • hexToBytes (149-156)
🪛 detekt (1.23.8)
vc-verifier/kotlin/vcverifier/src/main/java/io/mosip/vercred/vcverifier/credentialverifier/verifier/CwtVerifier.kt

[warning] 32-32: The caught exception is swallowed. The original exception could be lost.

(detekt.exceptions.SwallowedException)


[warning] 125-125: The caught exception is swallowed. The original exception could be lost.

(detekt.exceptions.SwallowedException)

Signed-off-by: jaswanthkumarpolisetty <[email protected]>
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In
@vc-verifier/kotlin/vcverifier/src/main/java/io/mosip/vercred/vcverifier/credentialverifier/verifier/CwtVerifier.kt:
- Around line 48-65: Delete the entire commented-out extractKid method block in
CwtVerifier (the commented function named extractKid) to remove dead code;
ensure no remaining references to extractKid exist in the class or callers, run
a build/compile to confirm nothing breaks, and commit the cleanup so Git history
retains the implementation if needed.
- Around line 106-112: The JavaDoc and exception handling around resolving the
public key are inaccurate: PublicKeyResolverFactory.get(issuer) returns a
non-null PublicKey so remove any null-checks, update the JavaDoc on the val
publicKey statement to state it resolves and returns a non-null PublicKey and
declare/cite that it may throw PublicKeyTypeNotSupportedException (not
PublicKeyNotFoundException), and change the catch/rethrow logic that currently
references PublicKeyNotFoundException to instead catch and handle or rethrow
PublicKeyTypeNotSupportedException so the actual thrown exception is handled
when calling PublicKeyResolverFactory.get and before calling
verifySignature(coseBytes, publicKey).
🧹 Nitpick comments (1)
vc-verifier/kotlin/vcverifier/build.gradle.kts (1)

13-15: Consider using per-dependency exclusions for better maintainability.

Global exclusions in configurations.all work but can be less explicit about which dependencies cause conflicts. Consider applying exclusions directly on dependencies that transitively bring in older BouncyCastle versions (e.g., on line 71's libs.cose.lib).

♻️ Alternative approach with per-dependency exclusions
 configurations.all {
     resolutionStrategy.force( "com.fasterxml.jackson.core:jackson-core:2.14.0")
-    exclude(group = "org.bouncycastle", module = "bcprov-jdk15on")
-    exclude(group = "org.bouncycastle", module = "bcpkix-jdk15on")
-    exclude(group = "org.bouncycastle", module = "bcutil-jdk15on")
 }

Then apply exclusions where they're actually needed:

implementation(libs.cose.lib) {
    exclude(group = "org.bouncycastle", module = "bcprov-jdk15on")
    exclude(group = "org.bouncycastle", module = "bcpkix-jdk15on")
    exclude(group = "org.bouncycastle", module = "bcutil-jdk15on")
}

This makes it clear which dependencies introduce the conflicting BouncyCastle versions.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 724202a and 1de3d02.

📒 Files selected for processing (2)
  • vc-verifier/kotlin/vcverifier/build.gradle.kts
  • vc-verifier/kotlin/vcverifier/src/main/java/io/mosip/vercred/vcverifier/credentialverifier/verifier/CwtVerifier.kt
🧰 Additional context used
🧬 Code graph analysis (1)
vc-verifier/kotlin/vcverifier/src/main/java/io/mosip/vercred/vcverifier/credentialverifier/verifier/CwtVerifier.kt (1)
vc-verifier/kotlin/vcverifier/src/main/java/io/mosip/vercred/vcverifier/utils/Util.kt (1)
  • hexToBytes (149-156)
🔇 Additional comments (5)
vc-verifier/kotlin/vcverifier/src/main/java/io/mosip/vercred/vcverifier/credentialverifier/verifier/CwtVerifier.kt (5)

1-21: LGTM! Clean imports and setup.

The imports are well-organized, and the past issue with the Android-specific import has been properly addressed. Using Util.hexToBytes avoids code duplication.


22-26: LGTM! Correct COSE_Sign1 structure validation.

The validation correctly enforces that COSE_Sign1 must be a 4-element array (protected headers, unprotected headers, payload, signature).


28-46: LGTM! Proper issuer extraction and scheme handling.

The method correctly extracts the issuer claim (CWT claim 1 per RFC 8392) and appropriately handles different URI schemes: DIDs are returned as-is, while HTTP(S) URIs are resolved to their JWKS endpoints.


67-76: LGTM! Correct CWT claims extraction.

The method properly extracts the payload from the COSE_Sign1 structure (index 2) and validates that CWT claims form a CBOR map per RFC 8392.


78-93: LGTM! Signature verification logic is sound.

The method correctly validates the COSE_Sign1 signature using the resolved public key. The safe cast at line 83-84 properly handles type mismatches.

Signed-off-by: jaswanthkumarpolisetty <[email protected]>
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
vc-verifier/kotlin/vcverifier/src/main/java/io/mosip/vercred/vcverifier/credentialverifier/verifier/CwtVerifier.kt (1)

76-100: Solid verification flow with correct exception handling.

The main verify() method implements a clean flow:

  1. Hex decode → CBOR decode → Structure validation
  2. Claims & issuer extraction
  3. Public key resolution via factory
  4. Signature verification

The exception handling at lines 90-98 is correct: specific exceptions (PublicKeyNotFoundException, SignatureVerificationException) are propagated, while unexpected exceptions are wrapped in UnknownException with context. The static analysis warning about "SwallowedException" is a false positive—no exceptions are silently discarded.

Minor consideration: Line 87 instantiates a new PublicKeyResolverFactory() on every verification. If the factory is stateless, this is fine. If it has initialization overhead or caches, consider making it a class-level property.

Based on learnings, PublicKeyResolverFactory().get(issuer) throws PublicKeyNotFoundException when resolution fails (rather than returning null), so the current exception handling is appropriate.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1de3d02 and c967142.

📒 Files selected for processing (2)
  • vc-verifier/kotlin/gradle/libs.versions.toml
  • vc-verifier/kotlin/vcverifier/src/main/java/io/mosip/vercred/vcverifier/credentialverifier/verifier/CwtVerifier.kt
🚧 Files skipped from review as they are similar to previous changes (1)
  • vc-verifier/kotlin/gradle/libs.versions.toml
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2026-01-07T10:57:30.150Z
Learnt from: jaswanthkumartw
Repo: inji/vc-verifier PR: 216
File: vc-verifier/kotlin/vcverifier/src/main/java/io/mosip/vercred/vcverifier/credentialverifier/verifier/CwtVerifier.kt:95-123
Timestamp: 2026-01-07T10:57:30.150Z
Learning: In the CwtVerifier class, the `PublicKeyResolverFactory().get(issuer)` method throws `PublicKeyNotFoundException` when a key cannot be resolved, rather than returning null.

Applied to files:

  • vc-verifier/kotlin/vcverifier/src/main/java/io/mosip/vercred/vcverifier/credentialverifier/verifier/CwtVerifier.kt
🪛 detekt (1.23.8)
vc-verifier/kotlin/vcverifier/src/main/java/io/mosip/vercred/vcverifier/credentialverifier/verifier/CwtVerifier.kt

[warning] 90-90: The caught exception is swallowed. The original exception could be lost.

(detekt.exceptions.SwallowedException)

🔇 Additional comments (4)
vc-verifier/kotlin/vcverifier/src/main/java/io/mosip/vercred/vcverifier/credentialverifier/verifier/CwtVerifier.kt (4)

22-26: LGTM! Correct COSE_Sign1 structure validation.

The validation correctly checks that the COSE object is an array with exactly 4 elements (protected headers, unprotected headers, payload, and signature), per the COSE_Sign1 specification.


48-57: LGTM! Correct CWT claims extraction and validation.

The method correctly extracts the payload from index 2 of the COSE_Sign1 array, decodes it as CBOR, and validates it's a map structure containing the CWT claims.


59-74: LGTM! Correct COSE signature verification.

The signature verification correctly:

  • Decodes and validates the COSE_Sign1 object structure
  • Creates a COSEKey from the resolved public key (with null algorithm parameter to infer from COSE headers)
  • Verifies the signature using the COSE library's validate() method

28-46: The implementation correctly follows RFC 8615 standards for well-known resource resolution. Using URI.resolve("/.well-known/jwks.json") with an absolute path is intentional—the JWKS endpoint should be at the domain root regardless of the issuer's path structure. For example, https://api.collab.mosip.net/tenant1 correctly resolves to https://api.collab.mosip.net/.well-known/jwks.json, which is the expected behavior.

Regarding the URISyntaxException at line 40: while an explicit catch with a more descriptive error message would be a minor improvement, the exception is already handled by the outer try-catch block at line 90 and converted to UnknownException, which is acceptable error handling.

Likely an incorrect or invalid review comment.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
vc-verifier/kotlin/vcverifier/src/main/java/io/mosip/vercred/vcverifier/keyResolver/Utils.kt (1)

133-157: Fix error message + reuse decoder in EC JWK parsing.
Line 140 throws "Missing 'x'" when y is missing, and you can reuse the shared base64Decoder.

Proposed fix
@@
     val yBase64 = jwk["y"]?.toString()
-        ?: throw PublicKeyResolutionFailedException("Missing 'x'")
-    val xBytes = Base64Decoder().decodeFromBase64Url(xBase64)
-    val yBytes = Base64Decoder().decodeFromBase64Url(yBase64)
+        ?: throw PublicKeyResolutionFailedException("Missing 'y'")
+    val xBytes = base64Decoder.decodeFromBase64Url(xBase64)
+    val yBytes = base64Decoder.decodeFromBase64Url(yBase64)
🤖 Fix all issues with AI agents
In
@vc-verifier/kotlin/vcverifier/src/main/java/io/mosip/vercred/vcverifier/credentialverifier/verifier/CwtVerifier.kt:
- Around line 48-62: The extractKid function converts KID byte arrays to String
using the platform default charset; change both occurrences where
protected[KID].GetByteString() and unprotected[KID].GetByteString() are wrapped
with String(...) to explicitly specify a charset (e.g., StandardCharsets.UTF_8)
to ensure deterministic KID values for matching against JWKS; update imports if
needed and keep the rest of the logic in extractKid, KID, coseObj, protected and
unprotected unchanged.
- Around line 95-109: The verify function calls extractClaims(coseObj) before
validateCoseStructure(coseObj), so malformed COSE objects can cause
extractClaims to throw unrelated exceptions; move the
validateCoseStructure(coseObj) call to immediately after decoding (before
extractClaims), then call extractClaims(coseObj), extractKid(coseObj),
extractIssuer(claims), resolve the public key via
PublicKeyResolverFactory().get(issuer, kid) and finally call
verifySignature(coseBytes, publicKey) so signature errors are properly
classified as SignatureVerificationException.

In
@vc-verifier/kotlin/vcverifier/src/main/java/io/mosip/vercred/vcverifier/keyResolver/types/jwks/JwksPublicKeyResolver.kt:
- Around line 15-35: The resolver currently reads kty from the JWK and passes
raw values like "EC" into getPublicKeyFromJWK, which expects internal constants
(e.g., JWK_KEY_TYPE_EC); fix resolve in JwksPublicKeyResolver.kt by normalizing
the extracted kty before calling getPublicKeyFromJWK (e.g., if kty == "EC" map
to JWK_KEY_TYPE_EC, likewise map "RSA" to JWK_KEY_TYPE_RSA if applicable), then
call getPublicKeyFromJWK(jwk, normalizedKeyType) so EC keys from JWKS resolve
correctly.
- Around line 23-27: The current JwksPublicKeyResolver selects the first key
when keyId is null which is ambiguous; change the resolution logic so that in
the block that computes "val jwk = keys.filterIsInstance<Map<String,
Any>>().firstOrNull { keyId == null || it["kid"] == keyId } ?: throw
PublicKeyNotFoundException(...)" you instead detect the keyId==null case: if
keyId is null and there are multiple candidate keys in "keys" throw a
PublicKeyNotFoundException indicating ambiguous/missing kid, otherwise proceed
to select the single candidate; ensure the exception message clearly states
ambiguity when multiple keys exist and still falls back to selecting the sole
key when exactly one candidate is present.

In
@vc-verifier/kotlin/vcverifier/src/main/java/io/mosip/vercred/vcverifier/keyResolver/Utils.kt:
- Around line 79-85: getPublicKeyFromJWK currently doesn't handle the raw JWK
kty value "EC", which causes EC JWKS resolution to fail; update the when branch
in getPublicKeyFromJWK to treat the string "EC" the same as
ES256K_KEY_TYPE_2019, ES256_KEY_TYPE_2019 and JWK_KEY_TYPE_EC so that
getECPublicKey(jwk) is called for both the constant values and the literal "EC";
ensure similar acceptance for other raw kty values if needed.
🧹 Nitpick comments (8)
vc-verifier/kotlin/vcverifier/src/test/java/io/mosip/vercred/vcverifier/credentialverifier/validator/CwtValidatorTest.kt (4)

13-15: Remove the empty setup method.

The setup() method contains no initialization logic and can be safely removed to reduce clutter.

🧹 Proposed cleanup
-    @BeforeEach
-    fun setup() {
-    }
-

34-58: Add a comment explaining the manual CBOR encoding.

The cborMapWithDuplicateKey function manually constructs CBOR bytes to create a malformed map with duplicate keys. A comment explaining why manual byte construction is necessary (to bypass the library's duplicate key prevention) would improve maintainability.

📝 Suggested documentation
     private fun cborMapWithDuplicateKey(
         key: Int,
         firstValue: CBORObject,
         secondValue: CBORObject
     ): ByteArray {
-        // CBOR map with 2 pairs, same key repeated
+        // Manually constructs a malformed CBOR map with duplicate keys.
+        // The CBORObject library prevents duplicate keys in maps, so we
+        // manually encode the bytes to bypass this validation and test
+        // the validator's handling of malformed CBOR.
         val map = CBORObject.NewArray().apply {

61-209: Consider extracting CBOR key constants for improved readability.

The tests use hard-coded integers for CBOR keys (1 for alg, 4 for exp, 5 for nbf, 6 for iat). Defining these as named constants would improve readability and maintainability.

💡 Example constants
companion object {
    private const val CBOR_KEY_ALG = 1
    private const val CBOR_KEY_EXP = 4
    private const val CBOR_KEY_NBF = 5
    private const val CBOR_KEY_IAT = 6
}

Then use them in tests:

val claims = CBORObject.NewMap().apply { Add(CBOR_KEY_EXP, past) }

77-77: Consider using the imported bytesToHex function.

The file imports bytesToHex from io.ipfs.multibase.Base16 (line 4) but manually converts bytes to hex using .joinToString("") { "%02x".format(it) } in multiple places. Using the imported function would improve consistency and reduce code duplication.

♻️ Example usage
-        val hex = coseArray.EncodeToBytes().joinToString("") { "%02x".format(it) }
+        val hex = bytesToHex(coseArray.EncodeToBytes())

Also applies to: 109-109, 129-129, 145-145, 203-203

vc-verifier/kotlin/vcverifier/src/main/java/io/mosip/vercred/vcverifier/keyResolver/types/jwks/JwksPublicKeyResolver.kt (1)

32-35: Preserve root cause + log stacktrace (current catch-all loses actionable details).
Right now you always throw PublicKeyNotFoundException("Public key string not found") and log only toString().

Proposed fix
@@
-        } catch (e: Exception) {
-            logger.severe("Error fetching public key string $e")
-            throw PublicKeyNotFoundException("Public key string not found")
-        }
+        } catch (e: Exception) {
+            logger.log(java.util.logging.Level.SEVERE, "Error resolving JWKS public key (uri=$uri, kid=$keyId)", e)
+            throw PublicKeyNotFoundException("JWKS public key resolution failed (uri=$uri, kid=$keyId)")
+        }
vc-verifier/kotlin/vcverifier/src/main/java/io/mosip/vercred/vcverifier/keyResolver/PublicKeyResolverFactory.kt (1)

18-25: Make JWKS detection robust: prefer URI.scheme + URI.path over string startsWith/endsWith.
This avoids missing cases like .../jwks.json?x=y and avoids accidental matches.

Proposed refactor
@@
-        val verificationMethodStr = verificationMethod.toString()
+        val verificationMethodStr = verificationMethod.toString()
+        val scheme = verificationMethod.scheme?.lowercase()
+        val path = verificationMethod.path ?: ""
         return when {
             verificationMethodStr.startsWith(DID_PREFIX) -> DidPublicKeyResolver().resolve(verificationMethod.toString())
-            verificationMethodStr.startsWith(HTTP_PREFIX) && verificationMethodStr.endsWith(JWKS_SUFFIX) -> JwksPublicKeyResolver().resolve(verificationMethod.toString(),keyId)
-            verificationMethodStr.startsWith(HTTP_PREFIX) -> HttpsPublicKeyResolver().resolve(verificationMethod.toString())
+            (scheme == "http" || scheme == "https") && path.endsWith("/$JWKS_SUFFIX") ->
+                JwksPublicKeyResolver().resolve(verificationMethod.toString(), keyId)
+            (scheme == "http" || scheme == "https") ->
+                HttpsPublicKeyResolver().resolve(verificationMethod.toString())
             else -> throw PublicKeyTypeNotSupportedException("Public Key type is not supported")
         }
vc-verifier/kotlin/vcverifier/src/main/java/io/mosip/vercred/vcverifier/keyResolver/Utils.kt (1)

115-129: Prefer explicit checks over require { throw ... } (current pattern is surprising).
This is harder to read and can confuse tooling; an if (...) throw PublicKeyResolutionFailedException(...) is clearer.

vc-verifier/kotlin/vcverifier/src/main/java/io/mosip/vercred/vcverifier/credentialverifier/verifier/CwtVerifier.kt (1)

110-118: Keep the root cause when wrapping into UnknownException.
Right now the stacktrace is lost; debugging verification failures will be much harder.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c967142 and a6891f9.

📒 Files selected for processing (7)
  • vc-verifier/kotlin/vcverifier/src/main/java/io/mosip/vercred/vcverifier/credentialverifier/validator/CwtValidator.kt
  • vc-verifier/kotlin/vcverifier/src/main/java/io/mosip/vercred/vcverifier/credentialverifier/verifier/CwtVerifier.kt
  • vc-verifier/kotlin/vcverifier/src/main/java/io/mosip/vercred/vcverifier/keyResolver/PublicKeyResolverFactory.kt
  • vc-verifier/kotlin/vcverifier/src/main/java/io/mosip/vercred/vcverifier/keyResolver/Utils.kt
  • vc-verifier/kotlin/vcverifier/src/main/java/io/mosip/vercred/vcverifier/keyResolver/types/jwks/JwksPublicKeyResolver.kt
  • vc-verifier/kotlin/vcverifier/src/test/java/io/mosip/vercred/vcverifier/credentialverifier/validator/CwtValidatorTest.kt
  • vc-verifier/kotlin/vcverifier/src/test/java/io/mosip/vercred/vcverifier/credentialverifier/verifier/CwtVerifierTest.kt
🚧 Files skipped from review as they are similar to previous changes (2)
  • vc-verifier/kotlin/vcverifier/src/main/java/io/mosip/vercred/vcverifier/credentialverifier/validator/CwtValidator.kt
  • vc-verifier/kotlin/vcverifier/src/test/java/io/mosip/vercred/vcverifier/credentialverifier/verifier/CwtVerifierTest.kt
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2026-01-07T10:57:37.322Z
Learnt from: jaswanthkumartw
Repo: inji/vc-verifier PR: 216
File: vc-verifier/kotlin/vcverifier/src/main/java/io/mosip/vercred/vcverifier/credentialverifier/verifier/CwtVerifier.kt:95-123
Timestamp: 2026-01-07T10:57:37.322Z
Learning: In the CwtVerifier class, the `PublicKeyResolverFactory().get(issuer)` method throws `PublicKeyNotFoundException` when a key cannot be resolved, rather than returning null.

Applied to files:

  • vc-verifier/kotlin/vcverifier/src/main/java/io/mosip/vercred/vcverifier/keyResolver/PublicKeyResolverFactory.kt
  • vc-verifier/kotlin/vcverifier/src/main/java/io/mosip/vercred/vcverifier/credentialverifier/verifier/CwtVerifier.kt
  • vc-verifier/kotlin/vcverifier/src/main/java/io/mosip/vercred/vcverifier/keyResolver/types/jwks/JwksPublicKeyResolver.kt
  • vc-verifier/kotlin/vcverifier/src/main/java/io/mosip/vercred/vcverifier/keyResolver/Utils.kt
🧬 Code graph analysis (1)
vc-verifier/kotlin/vcverifier/src/main/java/io/mosip/vercred/vcverifier/keyResolver/types/jwks/JwksPublicKeyResolver.kt (1)
vc-verifier/kotlin/vcverifier/src/main/java/io/mosip/vercred/vcverifier/keyResolver/Utils.kt (2)
  • getPublicKeyFromJWK (79-86)
  • getPublicKeyFromJWK (89-100)

Signed-off-by: jaswanthkumarpolisetty <[email protected]>
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In
@vc-verifier/kotlin/vcverifier/src/main/java/io/mosip/vercred/vcverifier/credentialverifier/verifier/CwtVerifier.kt:
- Around line 22-26: The validateCoseStructure method only checks array size;
add element-level type checks mirroring CwtValidator.validateCoseStructure:
verify coseObj[0] is a ByteString (protected header), coseObj[1] is a Map
(unprotected header), coseObj[2] is a ByteString (payload) and coseObj[3] is a
ByteString (signature), and throw SignatureVerificationException with clear
messages identifying the offending index and expected type (e.g., "Invalid
COSE_Sign1: protected header must be ByteString at index 0") so subsequent calls
like GetByteString() and ContainsKey(KID) fail fast with descriptive errors.
🧹 Nitpick comments (3)
vc-verifier/kotlin/vcverifier/src/main/java/io/mosip/vercred/vcverifier/credentialverifier/verifier/CwtVerifier.kt (3)

48-65: Consider charset handling for kid extraction.

Lines 55 and 61 convert the kid byte string to a String using the default charset (UTF-8). While kid values are typically ASCII or UTF-8 in practice, the COSE specification defines kid as a byte string (bstr) without mandating a specific encoding. If a kid contains non-UTF-8 bytes, the conversion could produce incorrect results.

Consider explicitly using UTF-8 charset or adding error handling for charset conversion failures, though this is an edge case unlikely to occur with real credentials.

♻️ Optional: Explicit charset handling
-                return String(protected[KID].GetByteString())
+                return String(protected[KID].GetByteString(), Charsets.UTF_8)
-            return String(unprotected[KID].GetByteString())
+            return String(unprotected[KID].GetByteString(), Charsets.UTF_8)

105-105: Use val instead of var for immutable variable.

The issuer variable is never reassigned, so it should be declared as val rather than var.

♻️ Proposed fix
-            var issuer = extractIssuer(claims)
+            val issuer = extractIssuer(claims)

109-117: Preserve the original exception as a cause when wrapping.

The else branch at line 114 wraps unexpected exceptions in UnknownException but only includes the message, losing the original exception's stack trace and details. This makes debugging more difficult when unexpected errors occur during verification.

Pass the original exception as the cause parameter to preserve the full error context.

♻️ Proposed fix to preserve exception cause
                 else -> throw UnknownException(
-                    "Error while verifying CWT credential: ${exception.message}"
+                    "Error while verifying CWT credential: ${exception.message}",
+                    exception
                 )
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a6891f9 and 6bd957b.

📒 Files selected for processing (2)
  • vc-verifier/kotlin/vcverifier/src/main/java/io/mosip/vercred/vcverifier/credentialverifier/verifier/CwtVerifier.kt
  • vc-verifier/kotlin/vcverifier/src/main/java/io/mosip/vercred/vcverifier/keyResolver/types/jwks/JwksPublicKeyResolver.kt
🚧 Files skipped from review as they are similar to previous changes (1)
  • vc-verifier/kotlin/vcverifier/src/main/java/io/mosip/vercred/vcverifier/keyResolver/types/jwks/JwksPublicKeyResolver.kt
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2026-01-07T10:57:37.322Z
Learnt from: jaswanthkumartw
Repo: inji/vc-verifier PR: 216
File: vc-verifier/kotlin/vcverifier/src/main/java/io/mosip/vercred/vcverifier/credentialverifier/verifier/CwtVerifier.kt:95-123
Timestamp: 2026-01-07T10:57:37.322Z
Learning: In the CwtVerifier class, the `PublicKeyResolverFactory().get(issuer)` method throws `PublicKeyNotFoundException` when a key cannot be resolved, rather than returning null.

Applied to files:

  • vc-verifier/kotlin/vcverifier/src/main/java/io/mosip/vercred/vcverifier/credentialverifier/verifier/CwtVerifier.kt
📚 Learning: 2026-01-09T05:57:50.933Z
Learnt from: jaswanthkumartw
Repo: inji/vc-verifier PR: 216
File: vc-verifier/kotlin/vcverifier/src/main/java/io/mosip/vercred/vcverifier/keyResolver/Utils.kt:79-85
Timestamp: 2026-01-09T05:57:50.933Z
Learning: In the file vc-verifier/kotlin/vcverifier/src/main/java/io/mosip/vercred/vcverifier/keyResolver/Utils.kt, the constant JWK_KEY_TYPE_EC is defined as "EC", so when this constant is used in pattern matching, it already covers the raw "EC" string value.

Applied to files:

  • vc-verifier/kotlin/vcverifier/src/main/java/io/mosip/vercred/vcverifier/credentialverifier/verifier/CwtVerifier.kt
🧬 Code graph analysis (1)
vc-verifier/kotlin/vcverifier/src/main/java/io/mosip/vercred/vcverifier/credentialverifier/verifier/CwtVerifier.kt (2)
vc-verifier/kotlin/vcverifier/src/main/java/io/mosip/vercred/vcverifier/utils/Util.kt (1)
  • hexToBytes (149-156)
vc-verifier/kotlin/vcverifier/src/main/java/io/mosip/vercred/vcverifier/credentialverifier/validator/CwtValidator.kt (1)
  • validateCoseStructure (63-110)
🪛 detekt (1.23.8)
vc-verifier/kotlin/vcverifier/src/main/java/io/mosip/vercred/vcverifier/credentialverifier/verifier/CwtVerifier.kt

[warning] 109-109: The caught exception is swallowed. The original exception could be lost.

(detekt.exceptions.SwallowedException)

🔇 Additional comments (3)
vc-verifier/kotlin/vcverifier/src/main/java/io/mosip/vercred/vcverifier/credentialverifier/verifier/CwtVerifier.kt (3)

28-46: LGTM!

The issuer extraction and URI resolution logic is correct. Using URI.resolve("/.well-known/jwks.json") properly replaces the path component for http/https issuers to construct the JWKS endpoint URL, while DID issuers are returned as-is for DID resolution.


67-76: LGTM!

The claims extraction properly decodes the payload from the COSE structure and validates that it's a CBOR Map, ensuring subsequent claim access operations will succeed.


78-93: LGTM!

The signature verification correctly uses the se.digg.cose library to validate the COSE_Sign1 signature against the resolved public key, with appropriate error handling.

Signed-off-by: jaswanthkumarpolisetty <[email protected]>
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In
@vc-verifier/kotlin/vcverifier/src/main/java/io/mosip/vercred/vcverifier/credentialverifier/verifier/CwtVerifier.kt:
- Around line 48-71: The extractKid function calls GetByteString() on header
values without validating their CBOR type, which can throw if the value is not a
ByteString; update extractKid to check that protected[KID].Type ==
CBORType.ByteString before calling GetByteString() and returning the UTF-8
String, and do the same for unprotected[KID], otherwise skip and continue to the
next check or return null; follow the defensive pattern used in
extractIssuer()/extractClaims() so only ByteString-typed KID values are
converted.
🧹 Nitpick comments (2)
vc-verifier/kotlin/vcverifier/src/main/java/io/mosip/vercred/vcverifier/credentialverifier/verifier/CwtVerifier.kt (2)

111-111: Use val instead of var for immutability.

The issuer variable is never reassigned, so it should be declared with val to indicate immutability.

♻️ Proposed fix
-            var issuer = extractIssuer(claims)
+            val issuer = extractIssuer(claims)

115-124: Preserve the original exception cause chain.

The exception handling wraps non-specific exceptions in UnknownException, but only includes the message. This loses the original exception's stack trace and cause chain, making debugging significantly harder.

♻️ Proposed fix to preserve exception cause
                 else -> throw UnknownException(
-                    "Error while verifying CWT credential: ${exception.message}"
+                    "Error while verifying CWT credential: ${exception.message}",
+                    exception
                 )

Note: Verify that the UnknownException constructor accepts a cause parameter. If not, consider updating the exception class to support this pattern.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6bd957b and 4a8e8b0.

📒 Files selected for processing (1)
  • vc-verifier/kotlin/vcverifier/src/main/java/io/mosip/vercred/vcverifier/credentialverifier/verifier/CwtVerifier.kt
🧰 Additional context used
🧠 Learnings (4)
📓 Common learnings
Learnt from: jaswanthkumartw
Repo: inji/vc-verifier PR: 216
File: vc-verifier/kotlin/vcverifier/src/main/java/io/mosip/vercred/vcverifier/credentialverifier/verifier/CwtVerifier.kt:22-26
Timestamp: 2026-01-09T06:10:16.923Z
Learning: In the CWT verification flow for the CwtVerifier and CwtValidator classes, validation is performed before verification. CwtValidator.validate() checks structure and claims first, then CwtVerifier.verify() performs signature verification. The verifier does not need to duplicate detailed structure validation since it's already handled by the validator.
📚 Learning: 2026-01-09T06:10:16.923Z
Learnt from: jaswanthkumartw
Repo: inji/vc-verifier PR: 216
File: vc-verifier/kotlin/vcverifier/src/main/java/io/mosip/vercred/vcverifier/credentialverifier/verifier/CwtVerifier.kt:22-26
Timestamp: 2026-01-09T06:10:16.923Z
Learning: In the CWT verification flow for the CwtVerifier and CwtValidator classes, validation is performed before verification. CwtValidator.validate() checks structure and claims first, then CwtVerifier.verify() performs signature verification. The verifier does not need to duplicate detailed structure validation since it's already handled by the validator.

Applied to files:

  • vc-verifier/kotlin/vcverifier/src/main/java/io/mosip/vercred/vcverifier/credentialverifier/verifier/CwtVerifier.kt
📚 Learning: 2026-01-07T10:57:37.322Z
Learnt from: jaswanthkumartw
Repo: inji/vc-verifier PR: 216
File: vc-verifier/kotlin/vcverifier/src/main/java/io/mosip/vercred/vcverifier/credentialverifier/verifier/CwtVerifier.kt:95-123
Timestamp: 2026-01-07T10:57:37.322Z
Learning: In the CwtVerifier class, the `PublicKeyResolverFactory().get(issuer)` method throws `PublicKeyNotFoundException` when a key cannot be resolved, rather than returning null.

Applied to files:

  • vc-verifier/kotlin/vcverifier/src/main/java/io/mosip/vercred/vcverifier/credentialverifier/verifier/CwtVerifier.kt
📚 Learning: 2026-01-09T05:57:50.933Z
Learnt from: jaswanthkumartw
Repo: inji/vc-verifier PR: 216
File: vc-verifier/kotlin/vcverifier/src/main/java/io/mosip/vercred/vcverifier/keyResolver/Utils.kt:79-85
Timestamp: 2026-01-09T05:57:50.933Z
Learning: In the file vc-verifier/kotlin/vcverifier/src/main/java/io/mosip/vercred/vcverifier/keyResolver/Utils.kt, the constant JWK_KEY_TYPE_EC is defined as "EC", so when this constant is used in pattern matching, it already covers the raw "EC" string value.

Applied to files:

  • vc-verifier/kotlin/vcverifier/src/main/java/io/mosip/vercred/vcverifier/credentialverifier/verifier/CwtVerifier.kt
🧬 Code graph analysis (1)
vc-verifier/kotlin/vcverifier/src/main/java/io/mosip/vercred/vcverifier/credentialverifier/verifier/CwtVerifier.kt (2)
vc-verifier/kotlin/vcverifier/src/main/java/io/mosip/vercred/vcverifier/utils/Util.kt (1)
  • hexToBytes (149-156)
vc-verifier/kotlin/vcverifier/src/main/java/io/mosip/vercred/vcverifier/credentialverifier/validator/CwtValidator.kt (1)
  • validateCoseStructure (63-110)
🪛 detekt (1.23.8)
vc-verifier/kotlin/vcverifier/src/main/java/io/mosip/vercred/vcverifier/credentialverifier/verifier/CwtVerifier.kt

[warning] 115-115: The caught exception is swallowed. The original exception could be lost.

(detekt.exceptions.SwallowedException)

🔇 Additional comments (2)
vc-verifier/kotlin/vcverifier/src/main/java/io/mosip/vercred/vcverifier/credentialverifier/verifier/CwtVerifier.kt (2)

22-26: Minimal structure validation is appropriate here.

Based on learnings, CwtValidator.validate() performs detailed structure checks before CwtVerifier.verify() is called, so this minimal validation (Array with 4 elements) is sufficient for the verifier's signature verification focus.


28-46: No issues found with URI resolution logic.

The implementation correctly uses URI.resolve("/.well-known/jwks.json") with an absolute path reference (leading /), which follows RFC 3986 semantics and ensures the JWKS endpoint is always resolved to the root path regardless of the issuer URL format (with/without trailing slashes or paths). Existing tests confirm this behavior works as intended for JWKS discovery.

Signed-off-by: jaswanthkumarpolisetty <[email protected]>
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (2)
vc-verifier/kotlin/vcverifier/src/main/java/io/mosip/vercred/vcverifier/credentialverifier/verifier/CwtVerifier.kt (2)

108-108: Use val instead of var.

The issuer variable is never reassigned, so it should be declared as val for immutability.

♻️ Proposed fix
-            var issuer = extractIssuer(claims)
+            val issuer = extractIssuer(claims)

109-109: Consider caching the PublicKeyResolverFactory instance.

The factory is stateless and has negligible instantiation cost, but caching it across verification calls could improve efficiency slightly. If instantiated frequently, consider making it a class-level property.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4a8e8b0 and c6574d7.

📒 Files selected for processing (1)
  • vc-verifier/kotlin/vcverifier/src/main/java/io/mosip/vercred/vcverifier/credentialverifier/verifier/CwtVerifier.kt
🧰 Additional context used
🧠 Learnings (4)
📓 Common learnings
Learnt from: jaswanthkumartw
Repo: inji/vc-verifier PR: 216
File: vc-verifier/kotlin/vcverifier/src/main/java/io/mosip/vercred/vcverifier/credentialverifier/verifier/CwtVerifier.kt:22-26
Timestamp: 2026-01-09T06:10:16.923Z
Learning: In the CWT verification flow for the CwtVerifier and CwtValidator classes, validation is performed before verification. CwtValidator.validate() checks structure and claims first, then CwtVerifier.verify() performs signature verification. The verifier does not need to duplicate detailed structure validation since it's already handled by the validator.
📚 Learning: 2026-01-09T06:10:16.923Z
Learnt from: jaswanthkumartw
Repo: inji/vc-verifier PR: 216
File: vc-verifier/kotlin/vcverifier/src/main/java/io/mosip/vercred/vcverifier/credentialverifier/verifier/CwtVerifier.kt:22-26
Timestamp: 2026-01-09T06:10:16.923Z
Learning: In the CWT verification flow for the CwtVerifier and CwtValidator classes, validation is performed before verification. CwtValidator.validate() checks structure and claims first, then CwtVerifier.verify() performs signature verification. The verifier does not need to duplicate detailed structure validation since it's already handled by the validator.

Applied to files:

  • vc-verifier/kotlin/vcverifier/src/main/java/io/mosip/vercred/vcverifier/credentialverifier/verifier/CwtVerifier.kt
📚 Learning: 2026-01-07T10:57:37.322Z
Learnt from: jaswanthkumartw
Repo: inji/vc-verifier PR: 216
File: vc-verifier/kotlin/vcverifier/src/main/java/io/mosip/vercred/vcverifier/credentialverifier/verifier/CwtVerifier.kt:95-123
Timestamp: 2026-01-07T10:57:37.322Z
Learning: In the CwtVerifier class, the `PublicKeyResolverFactory().get(issuer)` method throws `PublicKeyNotFoundException` when a key cannot be resolved, rather than returning null.

Applied to files:

  • vc-verifier/kotlin/vcverifier/src/main/java/io/mosip/vercred/vcverifier/credentialverifier/verifier/CwtVerifier.kt
📚 Learning: 2026-01-09T05:57:50.933Z
Learnt from: jaswanthkumartw
Repo: inji/vc-verifier PR: 216
File: vc-verifier/kotlin/vcverifier/src/main/java/io/mosip/vercred/vcverifier/keyResolver/Utils.kt:79-85
Timestamp: 2026-01-09T05:57:50.933Z
Learning: In the file vc-verifier/kotlin/vcverifier/src/main/java/io/mosip/vercred/vcverifier/keyResolver/Utils.kt, the constant JWK_KEY_TYPE_EC is defined as "EC", so when this constant is used in pattern matching, it already covers the raw "EC" string value.

Applied to files:

  • vc-verifier/kotlin/vcverifier/src/main/java/io/mosip/vercred/vcverifier/credentialverifier/verifier/CwtVerifier.kt
🧬 Code graph analysis (1)
vc-verifier/kotlin/vcverifier/src/main/java/io/mosip/vercred/vcverifier/credentialverifier/verifier/CwtVerifier.kt (2)
vc-verifier/kotlin/vcverifier/src/main/java/io/mosip/vercred/vcverifier/utils/Util.kt (1)
  • hexToBytes (149-156)
vc-verifier/kotlin/vcverifier/src/main/java/io/mosip/vercred/vcverifier/credentialverifier/validator/CwtValidator.kt (1)
  • validateCoseStructure (63-110)
🪛 detekt (1.23.8)
vc-verifier/kotlin/vcverifier/src/main/java/io/mosip/vercred/vcverifier/credentialverifier/verifier/CwtVerifier.kt

[warning] 112-112: The caught exception is swallowed. The original exception could be lost.

(detekt.exceptions.SwallowedException)

🔇 Additional comments (6)
vc-verifier/kotlin/vcverifier/src/main/java/io/mosip/vercred/vcverifier/credentialverifier/verifier/CwtVerifier.kt (6)

22-26: Lightweight structure validation as a defensive check.

This minimal validation checks the basic COSE_Sign1 structure. Based on learnings, detailed structure validation is already handled by CwtValidator.validate() before this verifier runs. This defensive check is acceptable but optional given the validation flow.


28-46: LGTM! Proper issuer extraction and resolution.

The method correctly extracts the issuer claim, validates its type, and appropriately handles different URI schemes (DID vs. HTTP/HTTPS with JWKS resolution).


48-67: LGTM! Correct key ID extraction.

The method properly extracts the key ID from COSE headers, checking protected headers first per COSE best practices, then falling back to unprotected headers.


70-79: LGTM! Proper claims extraction.

The method correctly extracts and validates the CWT claims structure from the COSE_Sign1 payload.


81-96: LGTM! Proper signature verification.

The method correctly decodes the COSE object, constructs the COSE key, and validates the signature using the library's verification method. The flow of returning true on success or throwing an exception is appropriate.


112-121: Exception handling is correct; static analysis warning is a false positive.

The exception handling properly re-throws PublicKeyNotFoundException and SignatureVerificationException, and wraps other exceptions in UnknownException. The static analysis tool incorrectly flags line 112 as swallowing exceptions, but the code explicitly re-throws known exceptions and wraps unknown ones—no exceptions are actually swallowed.

@mayuradesh
Copy link
Collaborator

@coderabbitai review this PR

@coderabbitai
Copy link

coderabbitai bot commented Jan 9, 2026

@mayuradesh: I'll review PR #216 which adds CWT (CBOR Web Token) format support for verifiable credentials. This includes new validation and verification classes, JWKS-based key resolution, and related test coverage.


🧠 Learnings used
Learnt from: jaswanthkumartw
Repo: inji/vc-verifier PR: 216
File: vc-verifier/kotlin/vcverifier/src/main/java/io/mosip/vercred/vcverifier/credentialverifier/verifier/CwtVerifier.kt:22-26
Timestamp: 2026-01-09T06:10:16.923Z
Learning: In the CWT verification flow for the CwtVerifier and CwtValidator classes, validation is performed before verification. CwtValidator.validate() checks structure and claims first, then CwtVerifier.verify() performs signature verification. The verifier does not need to duplicate detailed structure validation since it's already handled by the validator.
✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@mayuradesh mayuradesh merged commit 2e696c3 into inji:develop Jan 16, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

Comments