Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: SD-JWT verification #208

Open
wants to merge 25 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
51aa7d4
feat: verification sd-jwt
cristianIOHK Aug 22, 2024
2ca7155
feat: verify jwt verification after changes
cristianIOHK Aug 22, 2024
12dec3f
Merge branch 'main' into feature/ATL-6862
cristianIOHK Aug 22, 2024
9e351e1
feat: request credential support for SD-JWT
cristianIOHK Sep 6, 2024
cf9e827
feat: verification sd-jwt
cristianIOHK Sep 10, 2024
5ae06cb
Merge branch 'main' into feature/ATL-6862
cristianIOHK Sep 17, 2024
63f547b
feat: SD-JWT verification
cristianIOHK Sep 19, 2024
38a2fc6
feat: SD-JWT verification
cristianIOHK Sep 20, 2024
4b77970
feat: SD-JWT verification ready to test
cristianIOHK Sep 24, 2024
877e948
refactor: clean up code, remove deprecated unit tests
cristianIOHK Sep 25, 2024
51f4948
breaking change: remove deprecated methods
cristianIOHK Sep 25, 2024
9b4c0b9
Merge branch 'main' into feature/ATL-6862
cristianIOHK Sep 25, 2024
490e970
fix: linting issues
cristianIOHK Sep 25, 2024
e052705
fix: linting issues
cristianIOHK Sep 25, 2024
5355e93
fix: linting issues
cristianIOHK Sep 25, 2024
e729ac6
fix: linting issues
cristianIOHK Sep 25, 2024
7165cb2
fix: linting issues
cristianIOHK Sep 25, 2024
0e1feaf
feat: create prism did with Ed25519
cristianIOHK Sep 26, 2024
462e44e
feat: prism did support for Ed25519
cristianIOHK Sep 30, 2024
553247c
fix: Edge Agent should not be calling Apollo classes directly but Apo…
elribonazo Nov 5, 2024
052a206
fix: fix tests
elribonazo Nov 5, 2024
e073390
fix: improve pollux impl code
elribonazo Nov 5, 2024
0343bc7
fix: linting issues
elribonazo Nov 12, 2024
ab8ec6a
fix: improvements for ed25519 sdjwt signatures
elribonazo Nov 12, 2024
42132a3
fix: add tests for sdjwt
elribonazo Nov 25, 2024
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
1 change: 1 addition & 0 deletions edge-agent-sdk/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,7 @@ kotlin {
exclude(group = "com.nimbusds", module = "nimbus-jose-jwt")
}
implementation(kotlin("reflect"))
implementation("org.bouncycastle:bcprov-jdk15on:1.68")

implementation("com.apicatalog:titanium-json-ld-jre8:1.4.0")
implementation("org.glassfish:jakarta.json:2.0.1")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@ import org.hyperledger.identus.walletsdk.domain.models.keyManagement.PublicKey
import org.hyperledger.identus.walletsdk.domain.models.keyManagement.RawKey
import org.hyperledger.identus.walletsdk.domain.models.keyManagement.SeedKey
import org.hyperledger.identus.walletsdk.domain.models.keyManagement.StorableKey
import org.hyperledger.identus.walletsdk.domain.models.keyManagement.StorablePrivateKey
import org.hyperledger.identus.walletsdk.domain.models.keyManagement.TypeKey
import org.hyperledger.identus.walletsdk.logger.LogComponent
import org.hyperledger.identus.walletsdk.logger.LogLevel
Expand Down Expand Up @@ -463,37 +462,6 @@ class ApolloImpl(
}
}

/**
* Restores a private key from StorablePrivateKey.
*
* @param storablePrivateKey The StorablePrivateKey to restore the key from.
* @return The restored Key object.
*/
@Deprecated(
"This method has been deprecated and should no longer be used.",
ReplaceWith("restorePrivateKey(restoreIdentifier, privateKeyData)"),
DeprecationLevel.ERROR
)
override fun restorePrivateKey(storablePrivateKey: StorablePrivateKey): PrivateKey {
return when (storablePrivateKey.restorationIdentifier) {
"secp256k1+priv" -> {
Secp256k1PrivateKey(storablePrivateKey.data.base64UrlDecodedBytes)
}

"ed25519+priv" -> {
Ed25519PrivateKey(storablePrivateKey.data.base64UrlDecodedBytes)
}

"x25519+priv" -> {
X25519PrivateKey(storablePrivateKey.data.base64UrlDecodedBytes)
}

else -> {
throw PlutoError.InvalidRestorationIdentifier()
}
}
}

/**
* Restores a private key from StorablePrivateKey.
*
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
package org.hyperledger.identus.walletsdk.apollo.utils

import org.bouncycastle.crypto.params.Ed25519PrivateKeyParameters
import org.bouncycastle.asn1.ASN1Encodable
import org.bouncycastle.asn1.DEROctetString
import org.bouncycastle.asn1.edec.EdECObjectIdentifiers
import org.bouncycastle.asn1.pkcs.PrivateKeyInfo
import org.bouncycastle.asn1.x509.AlgorithmIdentifier
import org.bouncycastle.jce.provider.BouncyCastleProvider
import org.hyperledger.identus.apollo.base64.base64UrlEncoded
import org.hyperledger.identus.apollo.utils.KMMEdPrivateKey
Expand Down Expand Up @@ -128,9 +132,17 @@ class Ed25519PrivateKey(nativeValue: ByteArray) : PrivateKey(), SignableKey, Sto
get() = "ed25519+priv"

override fun jca(): java.security.PrivateKey {
val privateKeyParams = Ed25519PrivateKeyParameters(raw, 0)
val pkcs8Encoded = privateKeyParams.encoded
val keyFactory = KeyFactory.getInstance("Ed25519", BouncyCastleProvider())
return keyFactory.generatePrivate(PKCS8EncodedKeySpec(pkcs8Encoded))
val algId: AlgorithmIdentifier = AlgorithmIdentifier(EdECObjectIdentifiers.id_Ed25519)
// Wrap the private key bytes in a DEROctetString
val privateKey: ASN1Encodable = DEROctetString(raw)
// Create the PrivateKeyInfo structure
val privateKeyInfo: PrivateKeyInfo = PrivateKeyInfo(algId, privateKey)
// Get the PKCS#8 encoded bytes
val pkcs8Bytes: ByteArray = privateKeyInfo.getEncoded()
val keySpec = PKCS8EncodedKeySpec(pkcs8Bytes)
// Get a KeyFactory for Ed25519 using Bouncy Castle
val keyFactory = KeyFactory.getInstance("ed25519", BouncyCastleProvider())
// Generate the PrivateKey object
return keyFactory.generatePrivate(keySpec)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -69,12 +69,14 @@ constructor(
*/
override fun createPrismDID(
masterPublicKey: PublicKey,
services: Array<DIDDocument.Service>?
services: Array<DIDDocument.Service>?,
authenticationKeys: Array<PublicKey>
): DID {
return CastorShared.createPrismDID(
apollo = apollo,
masterPublicKey = masterPublicKey,
services = services
services = services,
authenticationKeys = authenticationKeys
)
}

Expand Down Expand Up @@ -123,7 +125,8 @@ constructor(
try {
val resolved = resolver.resolve(did)
return resolved
} catch (_: CastorError) {
} catch (err: CastorError) {
println(err.message)
}
}
throw Exception("No resolver could resolve the provided DID.")
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,22 @@
package org.hyperledger.identus.walletsdk.castor.did.prismdid

import org.hyperledger.identus.apollo.secp256k1.Secp256k1Lib
import org.hyperledger.identus.apollo.base64.base64UrlDecodedBytes
import org.hyperledger.identus.apollo.base64.base64UrlEncoded
import org.hyperledger.identus.protos.CompressedECKeyData
import org.hyperledger.identus.protos.ECKeyData
import org.hyperledger.identus.protos.KeyUsage
import org.hyperledger.identus.walletsdk.apollo.utils.Ed25519PublicKey
import org.hyperledger.identus.walletsdk.apollo.utils.Secp256k1PublicKey
import org.hyperledger.identus.walletsdk.domain.buildingblocks.Apollo
import org.hyperledger.identus.walletsdk.domain.models.CastorError
import org.hyperledger.identus.walletsdk.domain.models.Curve
import org.hyperledger.identus.walletsdk.domain.models.keyManagement.CurveKey
import org.hyperledger.identus.walletsdk.domain.models.keyManagement.CurvePointXKey
import org.hyperledger.identus.walletsdk.domain.models.keyManagement.CurvePointYKey
import org.hyperledger.identus.walletsdk.domain.models.keyManagement.KeyTypes
import org.hyperledger.identus.walletsdk.domain.models.keyManagement.PublicKey
import org.hyperledger.identus.walletsdk.domain.models.keyManagement.RawKey
import org.hyperledger.identus.walletsdk.domain.models.keyManagement.TypeKey
import pbandk.ByteArr
import kotlin.jvm.Throws

Expand Down Expand Up @@ -54,9 +63,27 @@ class PrismDIDPublicKey {
this.usage = proto.usage.fromProto()
this.keyData = when (proto.keyData) {
is org.hyperledger.identus.protos.PublicKey.KeyData.CompressedEcKeyData -> {
Secp256k1PublicKey(proto.keyData.value.data.array)
val compressedEcKeyData = proto.compressedEcKeyData!!
apollo.createPublicKey(
properties = mapOf(
TypeKey().property to KeyTypes.EC,
CurveKey().property to compressedEcKeyData.curve,
RawKey().property to compressedEcKeyData.data.array
)
)
}

is org.hyperledger.identus.protos.PublicKey.KeyData.EcKeyData -> {
val ecKeyData = proto.ecKeyData!!
apollo.createPublicKey(
properties = mapOf(
TypeKey().property to KeyTypes.EC,
CurveKey().property to ecKeyData.curve,
CurvePointXKey().property to ecKeyData.x.array.base64UrlEncoded,
CurvePointYKey().property to ecKeyData.y.array.base64UrlEncoded
)
)
}
else -> {
throw CastorError.InvalidPublicKeyEncoding("prism", "secp256k1")
}
Expand All @@ -69,14 +96,33 @@ class PrismDIDPublicKey {
* @return the converted Protobuf PublicKey object
*/
fun toProto(): org.hyperledger.identus.protos.PublicKey {
val compressedPublicKey = Secp256k1PublicKey(Secp256k1Lib().compressPublicKey(keyData.getValue()))
return org.hyperledger.identus.protos.PublicKey(
id = id,
usage = usage.toProto(),
keyData = org.hyperledger.identus.protos.PublicKey.KeyData.CompressedEcKeyData(
compressedPublicKey.toProto()
if (keyData.getCurve() == Curve.SECP256K1.value) {
val x = keyData.getProperty(CurvePointXKey().property)
val y = keyData.getProperty(CurvePointYKey().property)

return org.hyperledger.identus.protos.PublicKey(
id = id,
usage = usage.toProto(),
keyData = org.hyperledger.identus.protos.PublicKey.KeyData.EcKeyData(
ecKeyData = ECKeyData(
curve = keyData.getCurve(),
x = ByteArr(x.base64UrlDecodedBytes),
y = ByteArr(y.base64UrlDecodedBytes)
)
)
)
)
} else {
return org.hyperledger.identus.protos.PublicKey(
id = id,
usage = usage.toProto(),
keyData = org.hyperledger.identus.protos.PublicKey.KeyData.CompressedEcKeyData(
compressedEcKeyData = CompressedECKeyData(
curve = Curve.ED25519.value,
data = ByteArr(keyData.raw)
)
)
)
}
}

/**
Expand Down Expand Up @@ -128,6 +174,18 @@ fun Secp256k1PublicKey.toProto(): CompressedECKeyData {
)
}

/**
* Converts a Ed25519PublicKey object to a CompressedECKeyData object.
*
* @return the converted CompressedECKeyData object.
*/
fun Ed25519PublicKey.toProto(): CompressedECKeyData {
return CompressedECKeyData(
curve = Curve.ED25519.value,
data = ByteArr(raw)
)
}

/**
* Generates the identifier for a PrismDIDPublicKey.Usage based on the given index.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,26 +92,36 @@ internal class CastorShared {
internal fun createPrismDID(
apollo: Apollo,
masterPublicKey: PublicKey,
services: Array<DIDDocument.Service>?
services: Array<DIDDocument.Service>?,
authenticationKeys: Array<PublicKey>
): DID {
val pks: MutableList<org.hyperledger.identus.protos.PublicKey> = mutableListOf()

pks.add(
PrismDIDPublicKey(
apollo = apollo,
id = PrismDIDPublicKey.Usage.MASTER_KEY.defaultId(),
usage = PrismDIDPublicKey.Usage.MASTER_KEY,
keyData = masterPublicKey
).toProto()
)

// Add a public key for each authentication key
for (authKey in authenticationKeys) {
pks.add(
PrismDIDPublicKey(
apollo = apollo,
id = PrismDIDPublicKey.Usage.AUTHENTICATION_KEY.defaultId(),
usage = PrismDIDPublicKey.Usage.AUTHENTICATION_KEY,
keyData = authKey
).toProto()
)
}
val atalaOperation = AtalaOperation(
operation = AtalaOperation.Operation.CreateDid(
CreateDIDOperation(
didData = CreateDIDOperation.DIDCreationData(
publicKeys = listOf(
PrismDIDPublicKey(
apollo = apollo,
id = PrismDIDPublicKey.Usage.MASTER_KEY.defaultId(),
usage = PrismDIDPublicKey.Usage.MASTER_KEY,
keyData = masterPublicKey
).toProto(),
PrismDIDPublicKey(
apollo = apollo,
id = PrismDIDPublicKey.Usage.AUTHENTICATION_KEY.defaultId(),
usage = PrismDIDPublicKey.Usage.AUTHENTICATION_KEY,
keyData = masterPublicKey
).toProto()
),
publicKeys = pks,
services = services?.map {
Service(
id = it.id,
Expand All @@ -123,7 +133,6 @@ internal class CastorShared {
)
)
)

val encodedState = atalaOperation.encodeToByteArray()
val stateHash = SHA256().digest(encodedState).toHexString()
val base64State = encodedState.base64UrlEncoded
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@ interface Castor {
*/
fun createPrismDID(
masterPublicKey: PublicKey,
services: Array<DIDDocument.Service>?
services: Array<DIDDocument.Service>?,
authenticationKeys: Array<PublicKey>
): DID

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,19 +106,6 @@ interface Pluto {
*/
fun storeLinkSecret(linkSecret: String)

/**
* Stores the metadata associated with a credential request.
*
* @param name the unique name used to retrieve the stored metadata.
* @param metadata The metadata to store. It must be an instance of [CredentialRequestMeta].
*/
@Deprecated(
"This method has been deprecated and should no longer be used.",
ReplaceWith("storeCredentialMetadata(name, linkSecretName, json)"),
DeprecationLevel.ERROR
)
fun storeCredentialMetadata(name: String, metadata: CredentialRequestMeta)

/**
* Stores the metadata associated with a credential request.
*
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package org.hyperledger.identus.walletsdk.domain.models

import org.hyperledger.identus.walletsdk.domain.models.keyManagement.PrivateKey
import org.hyperledger.identus.walletsdk.domain.models.keyManagement.SignableKey as SignableKeyModel

sealed class CredentialOperationsOptions {
data class Schema(val id: String, val json: String) : CredentialOperationsOptions()
Expand All @@ -10,7 +11,7 @@ sealed class CredentialOperationsOptions {
data class LinkSecret(val id: String, val secret: String) : CredentialOperationsOptions()
data class SubjectDID(val did: DID) : CredentialOperationsOptions()
data class Entropy(val entropy: String) : CredentialOperationsOptions()
data class SignableKey(val key: SignableKey?) : CredentialOperationsOptions()
data class SignableKey(val key: SignableKeyModel?) : CredentialOperationsOptions()
data class ExportableKey(val key: PrivateKey?) : CredentialOperationsOptions()
data class ZkpPresentationParams(val attributes: Map<String, Boolean>, val predicates: List<String>) :
CredentialOperationsOptions()
Expand All @@ -20,5 +21,5 @@ sealed class CredentialOperationsOptions {
}

interface ProvableCredential {
suspend fun presentation(request: ByteArray, options: List<CredentialOperationsOptions>): String
suspend fun presentation(attachmentFormat: String, request: ByteArray, options: List<CredentialOperationsOptions>): String
}
Original file line number Diff line number Diff line change
Expand Up @@ -104,17 +104,25 @@ sealed interface VerifiableCredential {
}
}

sealed interface PresentationClaims
sealed interface PresentationClaims {
val claims: Map<String, InputFieldFilter>
}

data class JWTPresentationClaims(
val schema: String? = null,
val issuer: String? = null,
val claims: Map<String, InputFieldFilter>
override val claims: Map<String, InputFieldFilter>
) : PresentationClaims

data class SDJWTPresentationClaims(
val issuer: String? = null,
override val claims: Map<String, InputFieldFilter>
) : PresentationClaims

data class AnoncredsPresentationClaims(
val predicates: Map<String, AnoncredsInputFieldFilter>,
val attributes: Map<String, RequestedAttributes>
val attributes: Map<String, RequestedAttributes>,
override val claims: Map<String, InputFieldFilter> = emptyMap()
) : PresentationClaims

@OptIn(ExperimentalSerializationApi::class)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,19 +44,6 @@ interface KeyRestoration {
*/
fun restoreKey(key: JWK, index: Int? = null): Key

/**
* Restores a private key from StorablePrivateKey.
*
* @param storablePrivateKey The StorablePrivateKey to restore the key from.
* @return The restored Key object.
*/
@Deprecated(
"This method has been deprecated and should no longer be used.",
ReplaceWith("restorePrivateKey(restoreIdentifier, privateKeyData)"),
DeprecationLevel.ERROR
)
fun restorePrivateKey(storablePrivateKey: StorablePrivateKey): PrivateKey

/**
* Restores a private key using a restoration identifier and private key data encoded in base 64.
*
Expand Down
Loading
Loading