Skip to content

Commit

Permalink
feat: local key sign raw (ed25519) (#7)
Browse files Browse the repository at this point in the history
* breaking: upgraded crypto and did libs to jvm-15
* feat: local-key sign-raw
* cleanup
  • Loading branch information
mikeplotean authored Oct 22, 2023
1 parent 03454a6 commit 54c2e18
Show file tree
Hide file tree
Showing 7 changed files with 72 additions and 55 deletions.
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ build/
!**/src/test/**/build/

### IntelliJ IDEA ###
.idea
.idea/
*.iws
*.iml
*.ipr
Expand Down
8 changes: 0 additions & 8 deletions .idea/.gitignore

This file was deleted.

11 changes: 0 additions & 11 deletions .idea/misc.xml

This file was deleted.

8 changes: 4 additions & 4 deletions waltid-crypto/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -13,18 +13,18 @@ repositories {
}

java {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
sourceCompatibility = JavaVersion.VERSION_15
targetCompatibility = JavaVersion.VERSION_15
}

kotlin {
jvmToolchain(8)
jvmToolchain(15)
}

kotlin {
jvm {
compilations.all {
kotlinOptions.jvmTarget = "1.8" // JVM got Ed25519 at version 15
kotlinOptions.jvmTarget = "15" // JVM got Ed25519 at version 15
}
withJava()
tasks.withType<Test>().configureEach {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,21 @@ import com.nimbusds.jose.crypto.*
import com.nimbusds.jose.crypto.bc.BouncyCastleProviderSingleton
import com.nimbusds.jose.jwk.*
import com.nimbusds.jose.jwk.KeyType.*
import com.nimbusds.jose.util.Base64URL
import id.walt.crypto.utils.JsonUtils.toJsonElement
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.Transient
import kotlinx.serialization.json.*
import org.bouncycastle.asn1.ASN1BitString
import org.bouncycastle.asn1.ASN1Sequence
import java.security.PublicKey
import org.bouncycastle.asn1.DEROctetString
import org.bouncycastle.asn1.edec.EdECObjectIdentifiers
import org.bouncycastle.asn1.pkcs.PrivateKeyInfo
import org.bouncycastle.asn1.x509.AlgorithmIdentifier
import java.security.*
import java.security.spec.PKCS8EncodedKeySpec
import java.util.*

@Serializable
@SerialName("local")
Expand All @@ -39,8 +46,7 @@ actual class LocalKey actual constructor(
actual override suspend fun getPublicKeyRepresentation(): ByteArray = when (keyType) {
KeyType.Ed25519 -> _internalJwk.toOctetKeyPair().decodedX
KeyType.RSA -> getRsaPublicKeyBytes(_internalJwk.toRSAKey().toPublicKey())
KeyType.secp256r1 -> _internalJwk.toECKey().toPublicKey().encoded
KeyType.secp256k1 -> _internalJwk.toECKey().toPublicKey().encoded
KeyType.secp256k1, KeyType.secp256r1 -> _internalJwk.toECKey().toPublicKey().encoded
else -> TODO("Not yet implemented for: $keyType")
}

Expand Down Expand Up @@ -90,7 +96,12 @@ actual class LocalKey actual constructor(
}

actual override suspend fun signRaw(plaintext: ByteArray): ByteArray {
TODO("Not yet implemented")
check(hasPrivateKey) { "No private key is attached to this key!" }
val signature = getSignature()
signature.initSign(getPrivateKey())
signature.update(plaintext)
val sig = signature.sign()
return sig
}

/**
Expand All @@ -102,9 +113,7 @@ actual class LocalKey actual constructor(
actual override suspend fun signJws(plaintext: ByteArray, headers: Map<String, String>): String {
check(hasPrivateKey) { "No private key is attached to this key!" }
val jwsObject = JWSObject(
JWSHeader.Builder(_internalJwsAlgorithm)
.customParams(headers)
.build(),
JWSHeader.Builder(_internalJwsAlgorithm).customParams(headers).build(),
Payload(plaintext)
)

Expand Down Expand Up @@ -175,6 +184,34 @@ actual class LocalKey actual constructor(
return (pubPrim.getObjectAt(1) as ASN1BitString).octets
}

private fun getPrivateKey() = when (keyType) {
KeyType.secp256r1, KeyType.secp256k1 -> _internalJwk.toECKey().toPrivateKey()
KeyType.Ed25519 -> decodeEd25519RawPrivKey(_internalJwk.toOctetKeyPair().d.toString(), getKeyFactory())
KeyType.RSA -> _internalJwk.toRSAKey().toPrivateKey()
}

private fun getSignature(): Signature = when (keyType) {
KeyType.secp256k1, KeyType.secp256r1 -> Signature.getInstance("SHA256withECDSA")
KeyType.Ed25519 -> Signature.getInstance("Ed25519")
KeyType.RSA -> Signature.getInstance("SHA256withRSA")
}

private fun getKeyFactory() = when (keyType) {
KeyType.secp256r1, KeyType.secp256k1 -> KeyFactory.getInstance("ECDSA")
KeyType.Ed25519 -> KeyFactory.getInstance("Ed25519")
KeyType.RSA -> KeyFactory.getInstance("RSA")
}

private fun decodeEd25519RawPrivKey(base64: String, kf: KeyFactory): PrivateKey {
val privKeyInfo =
PrivateKeyInfo(
AlgorithmIdentifier(EdECObjectIdentifiers.id_Ed25519),
DEROctetString(Base64URL.from(base64).decode())
)
val pkcs8KeySpec = PKCS8EncodedKeySpec(privKeyInfo.encoded)
return kf.generatePrivate(pkcs8KeySpec)
}

actual companion object : LocalKeyCreator {
actual override suspend fun generate(type: KeyType, metadata: LocalKeyMetadata): LocalKey = JvmLocalKeyCreator.generate(type, metadata)
actual override suspend fun importJWK(jwk: String): Result<LocalKey> = JvmLocalKeyCreator.importJWK(jwk)
Expand Down
8 changes: 4 additions & 4 deletions waltid-did/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -13,19 +13,19 @@ repositories {
}

java {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
sourceCompatibility = JavaVersion.VERSION_15
targetCompatibility = JavaVersion.VERSION_15
}


kotlin {
jvmToolchain(11)
jvmToolchain(15)
}

kotlin {
jvm {
compilations.all {
kotlinOptions.jvmTarget = "11" // JVM got Ed25519 at version 15
kotlinOptions.jvmTarget = "15" // JVM got Ed25519 at version 15
}
withJava()
tasks.withType<Test>().configureEach {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import id.walt.did.dids.registrar.local.cheqd.models.job.didstates.Secret
import id.walt.did.dids.registrar.local.cheqd.models.job.didstates.SigningResponse
import id.walt.did.dids.registrar.local.cheqd.models.job.didstates.action.ActionDidState
import id.walt.did.dids.registrar.local.cheqd.models.job.didstates.didStateSerializationModule
import id.walt.did.dids.registrar.local.cheqd.models.job.didstates.failed.FailedDidState
import id.walt.did.dids.registrar.local.cheqd.models.job.didstates.finished.DidDocument
import id.walt.did.dids.registrar.local.cheqd.models.job.didstates.finished.FinishedDidState
import id.walt.did.dids.registrar.local.cheqd.models.job.request.JobCreateRequest
Expand All @@ -32,8 +33,6 @@ import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonElement
import kotlinx.serialization.json.encodeToJsonElement
import kotlin.io.encoding.Base64
import kotlin.io.encoding.ExperimentalEncodingApi

class DidCheqdRegistrar() : LocalRegistrarMethod("cheqd") {

Expand Down Expand Up @@ -64,8 +63,16 @@ class DidCheqdRegistrar() : LocalRegistrarMethod("cheqd") {
}
}

override suspend fun register(options: DidCreateOptions): DidResult =
registerByKey(LocalKey.generate(KeyType.Ed25519), options)

override suspend fun registerByKey(key: Key, options: DidCreateOptions): DidResult =
createDid(key, options.get<String>("network") ?: "testnet").let {
DidResult(it.id, id.walt.did.dids.document.DidDocument(DidCheqdDocument(it, key.exportJWKObject()).toMap()))
}

@OptIn(ExperimentalStdlibApi::class)
suspend fun createDid(key: Key, network: String): DidDocument = let {
private suspend fun createDid(key: Key, network: String): DidDocument = let {
if (key.keyType != KeyType.Ed25519) throw IllegalArgumentException("Key of type Ed25519 expected")
// step#0. get public key hex
val pubKeyHex = key.getPublicKeyRepresentation().toHexString()
Expand All @@ -78,6 +85,7 @@ class DidCheqdRegistrar() : LocalRegistrarMethod("cheqd") {
"&publicKeyHex=$pubKeyHex"
).bodyAsText()
// step#2. onboard did with cheqd registrar
//TODO: handle error responses (have only a 'message' field)
json.decodeFromString<DidGetResponse>(response).let { did ->
// step#2a. initialize
val job = initiateDidJob(didRegisterUrl, json.encodeToJsonElement(JobCreateRequest(did.didDoc)))
Expand All @@ -86,16 +94,16 @@ class DidCheqdRegistrar() : LocalRegistrarMethod("cheqd") {
// step#2c. finalize
job.jobId?.let {
// TODO: associate verificationMethodId with signature
(finalizeDidJob(
didRegisterUrl, it, did.didDoc.verificationMethod.first().id, signatures
).didState as? FinishedDidState)?.didDocument
?: throw IllegalArgumentException("Failed to finalize the did onboarding process")
val didState =
finalizeDidJob(didRegisterUrl, it, did.didDoc.verificationMethod.first().id, signatures).didState
(didState as? FinishedDidState)?.didDocument
?: throw IllegalArgumentException("Failed to finalize the did onboarding process.\nCheqd registrar returning \"${(didState as FailedDidState).description}\"")
} ?: throw Exception("Initialize job didn't return any jobId.")
}
}

// TODO: finish implementation
suspend fun deactivateDid(key: Key, did: String) {
private suspend fun deactivateDid(key: Key, did: String) {
val job = initiateDidJob(didDeactivateUrl, json.encodeToJsonElement(JobDeactivateRequest(did)))
val signatures = signPayload(key, job)
job.jobId?.let {
Expand All @@ -105,7 +113,7 @@ class DidCheqdRegistrar() : LocalRegistrarMethod("cheqd") {
} ?: throw Exception("Initialize job didn't return any jobId.")
}

fun updateDid(did: String) {
private fun updateDid(did: String) {
TODO()
}

Expand All @@ -131,24 +139,15 @@ class DidCheqdRegistrar() : LocalRegistrarMethod("cheqd") {
}.body<JobActionResponse>()
}

@OptIn(ExperimentalEncodingApi::class)
private suspend fun signPayload(key: Key, job: JobActionResponse): List<String> = let {
val state = (job.didState as? ActionDidState) ?: throw IllegalArgumentException("Unexpected did state")
val payloads = state.signingRequest.map {
Base64.decode(it.serializedPayload)
java.util.Base64.getDecoder().decode(it.serializedPayload)
}
// TODO: sign with key having alias from verification method

payloads.map {
Base64.encode(key.signRaw(it) as ByteArray)
java.util.Base64.getUrlEncoder().encodeToString(key.signRaw(it) as ByteArray)
}
}

override suspend fun register(options: DidCreateOptions): DidResult =
registerByKey(LocalKey.generate(KeyType.Ed25519), options)

override suspend fun registerByKey(key: Key, options: DidCreateOptions): DidResult =
createDid(key, options.get<String>("network") ?: "testnet").let {
DidResult(it.id, id.walt.did.dids.document.DidDocument(DidCheqdDocument(it, key.exportJWKObject()).toMap()))
}
}

0 comments on commit 54c2e18

Please sign in to comment.