Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ To integrate **vc-verifier** library into a Maven project , include below addit
To integrate **vc-verifier** library into a Gradle project , add below line in module level `build.gradle`.

dependencies {
implementation("io.mosip:vc-verifier:{{version-number}}")
implementation("io.mosip:vc-verifier-aar:{{version-number}}")
}

To avoid Duplicate classes error while building the application, include the below exclusion strategy in the build.gradle file.
Expand Down
2 changes: 2 additions & 0 deletions vc-verifier/kotlin/vcverifier/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,8 @@ tasks.register("jacocoTestReport", JacocoReport::class) {
executionData.setFrom(files("${layout.buildDirectory.get()}/jacoco/testDebugUnitTest.exec"))
}

tasks.register("prepareKotlinBuildScriptModel"){}

tasks.register<Jar>("jarRelease") {
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
dependsOn("assembleRelease")
Expand Down
4 changes: 2 additions & 2 deletions vc-verifier/kotlin/vcverifier/publish-artifact.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ publishing {
}
groupId = "io.mosip"
artifactId = "vcverifier-aar"
version = "1.2.0-SNAPSHOT"
version = "1.3.0-SNAPSHOT"
if (project.gradle.startParameter.taskNames.any { it.contains('assembleRelease') }) {
artifacts {
aar {
Expand All @@ -103,7 +103,7 @@ publishing {
artifact(tasks.named("jarRelease").get())
groupId = "io.mosip"
artifactId = "vcverifier-jar"
version = "1.2.0-SNAPSHOT"
version = "1.3.0-SNAPSHOT"
artifact(tasks.named("javadocJar").get())
artifact(tasks.named("sourcesJar").get())
pom {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ import io.mosip.vercred.vcverifier.constants.CredentialFormat
import io.mosip.vercred.vcverifier.constants.CredentialFormat.LDP_VC
import io.mosip.vercred.vcverifier.constants.CredentialValidatorConstants.ERROR_CODE_VC_EXPIRED
import io.mosip.vercred.vcverifier.constants.CredentialVerifierConstants.ERROR_CODE_VERIFICATION_FAILED
import io.mosip.vercred.vcverifier.constants.CredentialVerifierConstants.EXCEPTION_DURING_VERIFICATION
import io.mosip.vercred.vcverifier.constants.CredentialVerifierConstants.ERROR_MESSAGE_VERIFICATION_FAILED
import io.mosip.vercred.vcverifier.constants.CredentialVerifierConstants.EXCEPTION_DURING_VERIFICATION
import io.mosip.vercred.vcverifier.credentialverifier.CredentialVerifierFactory
import io.mosip.vercred.vcverifier.data.VerificationResult
import java.util.logging.Logger
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,24 +22,34 @@ class DidWebResolver(private val didUrl: String) {
private const val FRAGMENT = "(#.*)?"
private val DID_MATCHER = "^did:$METHOD:$METHOD_ID$PARAMS$PATH$QUERY$FRAGMENT$".toRegex()
private const val DOC_PATH = "/did.json"
private const val WELL_KNOWN_PATH = ".well-known"
}

fun resolve(): Map<String, Any> {
val parsedDid = parseDidUrl()
try {
val path = parsedDid.id.split(":").joinToString("/") {
URLDecoder.decode(it, StandardCharsets.UTF_8.name())
}
val url = "https://$path$DOC_PATH"
val url = constructDIDUrl(parsedDid)
return sendHTTPRequest(url, HTTP_METHOD.GET)
?: throw DidDocumentNotFound("Did document could not be fetched")
} catch (e: Exception) {
throw DidResolutionFailed(e.message)
}
}

private fun parseDidUrl(): ParsedDID {
private fun constructDIDUrl(parsedDid: ParsedDID): String {
val idComponents = parsedDid.id.split(":").map { it }
val baseDomain = idComponents.first()
val path = idComponents.drop(1).joinToString("/")
val urlPath = if (path.isEmpty()) {
WELL_KNOWN_PATH + DOC_PATH
} else {
path + DOC_PATH
}

return "https://$baseDomain/$urlPath"
}

private fun parseDidUrl(): ParsedDID {
val matchResult = DID_MATCHER.find(didUrl) ?: throw UnsupportedDidUrl()
val sections = matchResult.groupValues

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,12 +83,14 @@ object CredentialValidatorConstants {
val ALGORITHMS_SUPPORTED = listOf(
"PS256",
"RS256",
"EdDSA"
"EdDSA",
"ES256K"
)

val PROOF_TYPES_SUPPORTED = listOf(
"RsaSignature2018",
"Ed25519Signature2018",
"Ed25519Signature2020"
"Ed25519Signature2020",
"EcdsaSecp256k1Signature2019"
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ object CredentialVerifierConstants {

const val PUBLIC_KEY_PEM = "publicKeyPem"
const val PUBLIC_KEY_MULTIBASE = "publicKeyMultibase"
const val PUBLIC_KEY_JWK = "publicKeyJwk"
const val PUBLIC_KEY_HEX = "publicKeyHex"
const val VERIFICATION_METHOD = "verificationMethod"
const val KEY_TYPE = "type"

Expand All @@ -15,27 +17,29 @@ object CredentialVerifierConstants {

const val PS256_ALGORITHM = "SHA256withRSA/PSS"
const val RS256_ALGORITHM = "SHA256withRSA"
const val EC_ALGORITHM = "SHA256withECDSA"
const val ED25519_ALGORITHM = "Ed25519"
const val RSA_ALGORITHM = "RSA"
const val SECP256K1 = "secp256k1"

const val JWS_PS256_SIGN_ALGO_CONST = "PS256"
const val JWS_RS256_SIGN_ALGO_CONST = "RS256"
const val JWS_EDDSA_SIGN_ALGO_CONST = "EdDSA"

const val RSA_SIGNATURE = "RsaSignature2018"
const val ED25519_SIGNATURE_2018 = "Ed25519Signature2018"
const val ED25519_SIGNATURE_2020 = "Ed25519Signature2020"
const val JWS_ES256K_SIGN_ALGO_CONST = "ES256K"

const val RSA_KEY_TYPE = "RsaVerificationKey2018"
const val ED25519_KEY_TYPE_2018 = "Ed25519VerificationKey2018"
const val ED25519_KEY_TYPE_2020 = "Ed25519VerificationKey2020"
const val ES256K_KEY_TYPE_2019 = "EcdsaSecp256k1VerificationKey2019"


const val JWK_KEY_TYPE_EC = "EC"

const val EXCEPTION_DURING_VERIFICATION = "Exception during Verification: "
const val ERROR_MESSAGE_VERIFICATION_FAILED = "Verification Failed"
const val ERROR_CODE_VERIFICATION_FAILED = "ERR_SIGNATURE_VERIFICATION_FAILED"

// This is used to turn public key bytes into a buffer in DER format
const val DER_PUBLIC_KEY_PREFIX = "302a300506032b6570032100"

const val COMPRESSED_HEX_KEY_LENGTH = 33
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import info.weboftrust.ldsignatures.canonicalizer.URDNA2015Canonicalizer
import info.weboftrust.ldsignatures.util.JWSUtil
import io.ipfs.multibase.Multibase
import io.mosip.vercred.vcverifier.constants.CredentialVerifierConstants.JWS_EDDSA_SIGN_ALGO_CONST
import io.mosip.vercred.vcverifier.constants.CredentialVerifierConstants.JWS_ES256K_SIGN_ALGO_CONST
import io.mosip.vercred.vcverifier.constants.CredentialVerifierConstants.JWS_PS256_SIGN_ALGO_CONST
import io.mosip.vercred.vcverifier.constants.CredentialVerifierConstants.JWS_RS256_SIGN_ALGO_CONST
import io.mosip.vercred.vcverifier.exception.PublicKeyNotFoundException
Expand All @@ -16,6 +17,7 @@ import io.mosip.vercred.vcverifier.exception.UnknownException
import io.mosip.vercred.vcverifier.publicKey.PublicKeyGetterFactory
import io.mosip.vercred.vcverifier.signature.SignatureVerifier
import io.mosip.vercred.vcverifier.signature.impl.ED25519SignatureVerifierImpl
import io.mosip.vercred.vcverifier.signature.impl.ES256KSignatureVerifierImpl
import io.mosip.vercred.vcverifier.signature.impl.PS256SignatureVerifierImpl
import io.mosip.vercred.vcverifier.signature.impl.RS256SignatureVerifierImpl
import org.bouncycastle.jce.provider.BouncyCastleProvider
Expand All @@ -32,7 +34,8 @@ class LdpVerifier {
private val SIGNATURE_VERIFIER: Map<String, SignatureVerifier> = mapOf(
JWS_PS256_SIGN_ALGO_CONST to PS256SignatureVerifierImpl(),
JWS_RS256_SIGN_ALGO_CONST to RS256SignatureVerifierImpl(),
JWS_EDDSA_SIGN_ALGO_CONST to ED25519SignatureVerifierImpl()
JWS_EDDSA_SIGN_ALGO_CONST to ED25519SignatureVerifierImpl(),
JWS_ES256K_SIGN_ALGO_CONST to ES256KSignatureVerifierImpl()
)

init {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,39 +1,45 @@
package io.mosip.vercred.vcverifier.publicKey

import com.fasterxml.jackson.databind.ObjectMapper
import io.ipfs.multibase.Base58
import io.mosip.vercred.vcverifier.constants.CredentialVerifierConstants.COMPRESSED_HEX_KEY_LENGTH
import io.mosip.vercred.vcverifier.constants.CredentialVerifierConstants.DER_PUBLIC_KEY_PREFIX
import io.mosip.vercred.vcverifier.constants.CredentialVerifierConstants.ED25519_ALGORITHM
import io.mosip.vercred.vcverifier.constants.CredentialVerifierConstants.ED25519_KEY_TYPE_2018
import io.mosip.vercred.vcverifier.constants.CredentialVerifierConstants.ED25519_KEY_TYPE_2020
import io.mosip.vercred.vcverifier.constants.CredentialVerifierConstants.ES256K_KEY_TYPE_2019
import io.mosip.vercred.vcverifier.constants.CredentialVerifierConstants.JWK_KEY_TYPE_EC
import io.mosip.vercred.vcverifier.constants.CredentialVerifierConstants.RSA_ALGORITHM
import io.mosip.vercred.vcverifier.constants.CredentialVerifierConstants.RSA_KEY_TYPE
import io.mosip.vercred.vcverifier.constants.CredentialVerifierConstants.SECP256K1
import io.mosip.vercred.vcverifier.exception.PublicKeyNotFoundException
import io.mosip.vercred.vcverifier.exception.PublicKeyTypeNotSupportedException
import io.mosip.vercred.vcverifier.utils.Encoder
import org.bouncycastle.jce.ECNamedCurveTable
import org.bouncycastle.jce.provider.BouncyCastleProvider
import org.bouncycastle.jce.spec.ECNamedCurveSpec
import org.bouncycastle.util.encoders.Hex
import org.bouncycastle.util.io.pem.PemReader
import java.io.StringReader
import java.math.BigInteger
import java.security.KeyFactory
import java.security.PublicKey
import java.security.interfaces.ECPublicKey
import java.security.spec.ECParameterSpec
import java.security.spec.ECPublicKeySpec
import java.security.spec.X509EncodedKeySpec

private var provider: BouncyCastleProvider = BouncyCastleProvider()

fun isPublicKeyMultibase(publicKeyMultibase: String): Boolean {
//ref: https://w3c.github.io/vc-di-eddsa/#multikey
val rawPublicKeyWithHeader = Base58.decode(publicKeyMultibase.substring(1))
return rawPublicKeyWithHeader.size > 2 &&
rawPublicKeyWithHeader[0] == 0xed.toByte() &&
rawPublicKeyWithHeader[1] == 0x01.toByte()
}

fun isPemPublicKey(str: String) = str.contains("BEGIN PUBLIC KEY")

private val PUBLIC_KEY_ALGORITHM: Map<String, String> = mapOf(
RSA_KEY_TYPE to RSA_ALGORITHM,
ED25519_KEY_TYPE_2018 to ED25519_ALGORITHM,
ED25519_KEY_TYPE_2020 to ED25519_ALGORITHM
ED25519_KEY_TYPE_2020 to ED25519_ALGORITHM,
)

private const val SECP256K1_PRIME_MODULUS =
"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F"

fun getPublicKeyObjectFromPemPublicKey(publicKeyPem: String, keyType: String): PublicKey {
try {
val strReader = StringReader(publicKeyPem)
Expand All @@ -48,6 +54,39 @@ fun getPublicKeyObjectFromPemPublicKey(publicKeyPem: String, keyType: String): P
}
}


fun getPublicKeyFromJWK(jwkStr: String, keyType: String): PublicKey {
val objectMapper = ObjectMapper()
val jwk: Map<String, String> =
objectMapper.readValue(jwkStr, Map::class.java) as Map<String, String>

return when (keyType) {
ES256K_KEY_TYPE_2019 -> getECPublicKey(jwk)
else -> throw PublicKeyTypeNotSupportedException("Unsupported key type: $keyType")
}
}


private fun getECPublicKey(jwk: Map<String, String>): PublicKey {
val curve = jwk["crv"] ?: throw IllegalArgumentException("Missing 'crv' field for EC key")
val xBytes = Encoder().decodeFromBase64UrlFormatEncoded(jwk["x"]!!)
val yBytes = Encoder().decodeFromBase64UrlFormatEncoded(jwk["y"]!!)

val x = BigInteger(1, xBytes)
val y = BigInteger(1, yBytes)
val ecPoint = java.security.spec.ECPoint(x, y)

val ecSpec = when (curve) {
SECP256K1 -> ECNamedCurveTable.getParameterSpec(SECP256K1)
else -> throw IllegalArgumentException("Unsupported EC curve: $curve")
}

val ecParameterSpec = ECNamedCurveSpec(curve, ecSpec.curve, ecSpec.g, ecSpec.n)
val pubKeySpec = ECPublicKeySpec(ecPoint, ecParameterSpec)
val keyFactory = KeyFactory.getInstance(JWK_KEY_TYPE_EC, provider)
return keyFactory.generatePublic(pubKeySpec)
}

fun getPublicKeyObjectFromPublicKeyMultibase(publicKeyPem: String, keyType: String): PublicKey {
try {
val rawPublicKeyWithHeader = Base58.decode(publicKeyPem.substring(1))
Expand All @@ -61,3 +100,52 @@ fun getPublicKeyObjectFromPublicKeyMultibase(publicKeyPem: String, keyType: Stri
}
}

fun getPublicKeyFromHex(hexKey: String, keyType: String): PublicKey {
return when (keyType) {
ES256K_KEY_TYPE_2019 -> getECPublicKeyFromHex(hexKey)
else -> throw PublicKeyTypeNotSupportedException("Unsupported key type: $keyType")
}
}

fun getECPublicKeyFromHex(hexKey: String): PublicKey {
val keyFactory = KeyFactory.getInstance(JWK_KEY_TYPE_EC, provider)
val keyBytes = hexStringToByteArray(hexKey)
val ecPoint = decodeSecp256k1PublicKey(keyBytes)
val ecSpec = secp256k1Params()
val pubKeySpec = ECPublicKeySpec(ecPoint, ecSpec)

return keyFactory.generatePublic(pubKeySpec) as ECPublicKey
}

private fun hexStringToByteArray(hex: String): ByteArray {
return BigInteger(hex, 16).toByteArray().dropWhile { it == 0.toByte() }.toByteArray()
}


private fun decodeSecp256k1PublicKey(keyBytes: ByteArray): java.security.spec.ECPoint {
require(keyBytes.size == COMPRESSED_HEX_KEY_LENGTH) { "Invalid compressed public key length" }

val x = BigInteger(1, keyBytes.copyOfRange(1, keyBytes.size))
val y = recoverYCoordinate(x, keyBytes[0] == 3.toByte())

return java.security.spec.ECPoint(x, y)
}

// Recover the Y-coordinate from X using the Secp256k1 curve equation
private fun recoverYCoordinate(x: BigInteger, odd: Boolean): BigInteger {
val p = BigInteger(SECP256K1_PRIME_MODULUS, 16)
val b = BigInteger.valueOf(7)

val rhs = (x.modPow(BigInteger.valueOf(3), p).add(b)).mod(p)
val y = rhs.modPow(p.add(BigInteger.ONE).divide(BigInteger.valueOf(4)), p)

return if (y.testBit(0) == odd) y else p.subtract(y)
}

private fun secp256k1Params(): ECParameterSpec {
val params = ECNamedCurveTable.getParameterSpec(SECP256K1)
return ECNamedCurveSpec(SECP256K1, params.curve, params.g, params.n, params.h)
}



Loading