Skip to content

Commit

Permalink
Synchronized with waltid-sdjwt
Browse files Browse the repository at this point in the history
  • Loading branch information
waltkb committed Nov 20, 2023
1 parent 8db6059 commit e50ec53
Show file tree
Hide file tree
Showing 9 changed files with 275 additions and 5 deletions.
58 changes: 56 additions & 2 deletions waltid-sdjwt/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,16 @@ repositories {
mavenCentral()
}

@OptIn(org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi::class)
kotlin {
jvm {
jvmToolchain(16)
jvmToolchain(18) // 16 possible?
withJava()
testRuns["test"].executionTask.configure {
useJUnitPlatform()
}
}

js(IR) {
browser {
commonWebpackConfig(Action {
Expand Down Expand Up @@ -50,6 +52,37 @@ kotlin {
isMingwX64 -> mingwX64("native")
else -> throw GradleException("Host OS is not supported in Kotlin/Native.")
}
when(hostOs) {
"Mac OS X" -> listOf (
iosArm64(),
iosX64(),
iosSimulatorArm64()
)
else -> listOf()
}.forEach {
val platform = when (it.name) {
"iosArm64" -> "iphoneos"
else -> "iphonesimulator"
}

it.binaries.framework {
baseName = "shared"
}

it.compilations.getByName("main") {
cinterops.create("id.walt.sdjwt.cinterop.ios") {
val interopTask = tasks[interopProcessingTaskName]
interopTask.dependsOn(":waltid-sd-jwt-ios:build${platform.capitalize()}")

defFile("$projectDir/src/nativeInterop/cinterop/waltid-sd-jwt-ios.def")
packageName("id.walt.sdjwt.cinterop.ios")
includeDirs("$projectDir/waltid-sd-jwt-ios/build/Release-$platform/include/")

headers("$projectDir/waltid-sd-jwt-ios/build/Release-$platform/include/waltid_sd_jwt_ios/waltid_sd_jwt_ios-Swift.h")
}
}
}

val kryptoVersion = "4.0.10"


Expand Down Expand Up @@ -94,6 +127,27 @@ kotlin {
}
val nativeMain by getting
val nativeTest by getting

if (hostOs == "Mac OS X") {
val iosArm64Main by getting
val iosSimulatorArm64Main by getting
val iosX64Main by getting
val iosMain by creating {
dependsOn(commonMain)
iosArm64Main.dependsOn(this)
iosSimulatorArm64Main.dependsOn(this)
iosX64Main.dependsOn(this)
}
val iosArm64Test by getting
val iosSimulatorArm64Test by getting
val iosX64Test by getting
val iosTest by creating {
dependsOn(commonTest)
iosArm64Test.dependsOn(this)
iosSimulatorArm64Test.dependsOn(this)
iosX64Test.dependsOn(this)
}
}
}

publishing {
Expand Down Expand Up @@ -133,7 +187,7 @@ npmPublish {
println("NPM token: ${hasNPMToken}")
println("Release build: ${isReleaseBuild}")
if (isReleaseBuild && hasNPMToken) {
readme.set(File("README.md"))
readme.set(File("NPM_README.md"))
register("npmjs") {
uri.set(uri("https://registry.npmjs.org"))
authToken.set(secretNpmToken)
Expand Down
15 changes: 15 additions & 0 deletions waltid-sdjwt/src/iosMain/kotlin/id/walt/sdjwt/ByteArray+NSDAta.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package id.walt.sdjwt

import kotlinx.cinterop.ExperimentalForeignApi
import kotlinx.cinterop.addressOf
import kotlinx.cinterop.pin
import platform.Foundation.NSData
import platform.Foundation.create

@OptIn(ExperimentalForeignApi::class)
internal inline fun ByteArray.toData(offset: Int = 0, length: Int = size - offset): NSData {
require(offset + length <= size) { "offset + length > size" }
if (isEmpty()) return NSData()
val pinned = pin()
return NSData.create(pinned.addressOf(offset), length.toULong()) { _, _ -> pinned.unpin() }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import id.walt.sdjwt.JWTCryptoProvider
import id.walt.sdjwt.JwtVerificationResult
import id.walt.sdjwt.cinterop.ios.DS_Operations
import kotlinx.cinterop.ExperimentalForeignApi
import kotlinx.serialization.json.JsonObject
import platform.Security.*
import kotlin.js.ExperimentalJsExport

@ExperimentalJsExport
@OptIn(ExperimentalForeignApi::class)
class DigitalSignaturesJWTCryptoProvider(private val algorithm: String, private val key: SecKeyRef) :
JWTCryptoProvider {
override fun sign(payload: JsonObject, keyID: String?, typ: String): String {

val result = DS_Operations.signWithBody(
body = payload.toString(),
alg = algorithm,
key = key,
typ = typ,
keyId = keyID
)

return when {
result.success() -> result.data()!!
else -> result.errorMessage() ?: ""
}
}

override fun verify(jwt: String): JwtVerificationResult {
val result = DS_Operations.verifyWithJws(jws = jwt, key = key)

return when {
result.success() -> JwtVerificationResult(result.success()!!)
else -> JwtVerificationResult(false, message = result.errorMessage() ?: "")
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package id.walt.sdjwt

import id.walt.sdjwt.cinterop.ios.*
import kotlinx.cinterop.ExperimentalForeignApi
import kotlinx.cinterop.addressOf
import kotlinx.cinterop.pin
import kotlinx.serialization.json.JsonObject
import platform.Foundation.NSData
import platform.Foundation.create
import kotlin.js.ExperimentalJsExport

@ExperimentalJsExport
@OptIn(ExperimentalForeignApi::class)
class HMACJWTCryptoProvider(private val algorithm: String, private val key: ByteArray) :
JWTCryptoProvider {
override fun sign(payload: JsonObject, keyID: String?, typ: String): String {

val result = HMAC_Operations.signWithBody(
body = payload.toString(),
alg = algorithm,
key = key.toData(),
typ = typ,
keyId = keyID
)

return when {
result.success() -> result.data()!!
else -> result.errorMessage() ?: ""
}
}

override fun verify(jwt: String): JwtVerificationResult {
val result = HMAC_Operations.verifyWithJws(jws = jwt, key = key.toData())

return when {
result.success() -> JwtVerificationResult(result.success()!!)
else -> JwtVerificationResult(false, message = result.errorMessage() ?: "")
}
}
}
9 changes: 9 additions & 0 deletions waltid-sdjwt/src/iosMain/kotlin/id/walt/sdjwt/JWTClaimsSet.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package id.walt.sdjwt

/**
* Expected class for JWT claim set in platform specific implementation. Not necessarily required.
*/
actual class JWTClaimsSet {
actual override fun toString(): String = ""

}
104 changes: 104 additions & 0 deletions waltid-sdjwt/src/iosTest/kotlin/id.walt.sdjwt/SDJwtTestIOS.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
package id.walt.sdjwt

import io.kotest.assertions.json.shouldMatchJson
import io.kotest.matchers.collections.shouldHaveSize
import io.kotest.matchers.maps.shouldContainKey
import io.kotest.matchers.maps.shouldNotContainKey
import io.kotest.matchers.shouldBe
import kotlinx.serialization.json.JsonElement
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.JsonPrimitive
import kotlinx.serialization.json.jsonArray
import kotlinx.serialization.json.jsonPrimitive
import kotlin.test.Test

class SDJwtTestIOS {
private val sharedSecret = "ef23f749-7238-481a-815c-f0c2157dfa8e"

@Test
fun testSignJwt() {
val cryptoProvider = HMACJWTCryptoProvider("HS256", sharedSecret.encodeToByteArray())

val originalSet = mutableMapOf<String, JsonElement> (
"sub" to JsonPrimitive("123"),
"aud" to JsonPrimitive("456")
)

val originalClaimsSet = JsonObject(originalSet)

// Create undisclosed claims set, by removing e.g. subject property from original claims set
val undisclosedSet = mutableMapOf<String, JsonElement> (
"aud" to JsonPrimitive("456")
)

val undisclosedClaimsSet = JsonObject(undisclosedSet)

// Create SD payload by comparing original claims set with undisclosed claims set
val sdPayload = SDPayload.createSDPayload(originalClaimsSet, undisclosedClaimsSet)

// Create and sign SD-JWT using the generated SD payload and the previously configured crypto provider
val sdJwt = SDJwt.sign(sdPayload, cryptoProvider)
// Print SD-JWT
println(sdJwt)

sdJwt.undisclosedPayload shouldNotContainKey "sub"
sdJwt.undisclosedPayload shouldContainKey SDJwt.DIGESTS_KEY
sdJwt.undisclosedPayload shouldContainKey "aud"
sdJwt.disclosures shouldHaveSize 1
sdJwt.digestedDisclosures[sdJwt.undisclosedPayload[SDJwt.DIGESTS_KEY]!!.jsonArray[0].jsonPrimitive.content]!!.key shouldBe "sub"
sdJwt.fullPayload.toString() shouldMatchJson originalClaimsSet.toString()

sdJwt.verify(cryptoProvider).verified shouldBe true
}

@Test
fun presentSDJwt() {
// parse previously created SD-JWT
val sdJwt =
SDJwt.parse("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJhdWQiOiI0NTYiLCJfc2QiOlsiaGx6ZmpmMDRvNVpzTFIyNWhhNGMtWS05SFcyRFVseGNnaU1ZZDMyNE5nWSJdfQ.2fsLqzujWt0hS0peLS8JLHyyo3D5KCDkNnHcBYqQwVo~WyJ4RFk5VjBtOG43am82ZURIUGtNZ1J3Iiwic3ViIiwiMTIzIl0")

// present without disclosing SD fields
val presentedUndisclosedJwt = sdJwt.present(discloseAll = false)
println(presentedUndisclosedJwt)

// present disclosing all SD fields
val presentedDisclosedJwt = sdJwt.present(discloseAll = true)
println(presentedDisclosedJwt)

// present disclosing selective fields, using SDMap
val presentedSelectiveJwt = sdJwt.present(SDMapBuilder().addField("sub", true).build())
println(presentedSelectiveJwt)

// present disclosing fields, using JSON paths
val presentedSelectiveJwt2 = sdJwt.present(SDMap.generateSDMap(listOf("sub")))
println(presentedSelectiveJwt2)

}

@Test
fun parseAndVerify() {
// Create SimpleJWTCryptoProvider with MACSigner and MACVerifier
val cryptoProvider = HMACJWTCryptoProvider("HS256", sharedSecret.encodeToByteArray())
val undisclosedJwt = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJhdWQiOiI0NTYiLCJfc2QiOlsiaGx6ZmpmMDRvNVpzTFIyNWhhNGMtWS05SFcyRFVseGNnaU1ZZDMyNE5nWSJdfQ.2fsLqzujWt0hS0peLS8JLHyyo3D5KCDkNnHcBYqQwVo~"

// verify and parse presented SD-JWT with all fields undisclosed, throws Exception if verification fails!
val parseAndVerifyResult = SDJwt.verifyAndParse(undisclosedJwt, cryptoProvider)

// print full payload with disclosed fields only
println("Undisclosed JWT payload:")
println(parseAndVerifyResult.sdJwt.fullPayload.toString())

// alternatively parse and verify in 2 steps:
val parsedUndisclosedJwt = SDJwt.parse(undisclosedJwt)
val isValid = parsedUndisclosedJwt.verify(cryptoProvider).verified
println("Undisclosed SD-JWT verified: $isValid")

val parsedDisclosedJwtVerifyResult = SDJwt.verifyAndParse(
"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJhdWQiOiI0NTYiLCJfc2QiOlsiaGx6ZmpmMDRvNVpzTFIyNWhhNGMtWS05SFcyRFVseGNnaU1ZZDMyNE5nWSJdfQ.2fsLqzujWt0hS0peLS8JLHyyo3D5KCDkNnHcBYqQwVo~WyJ4RFk5VjBtOG43am82ZURIUGtNZ1J3Iiwic3ViIiwiMTIzIl0~",
cryptoProvider
)
// print full payload with disclosed fields
println("Disclosed JWT payload:")
println(parsedDisclosedJwtVerifyResult.sdJwt.fullPayload.toString())
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,9 @@ open class SimpleAsyncJWTCryptoProvider(
console.log("SIGNING", payload.toString())
jose.SignJWT(JSON.parse(payload.toString())).setProtectedHeader(buildJsonObject {
put("alg", algorithm)
put("cty", "credential-claims-set+json")
put("typ", "vc+sd-jwt")
put("typ", "JWT")
//put("cty", "credential-claims-set+json")
//put("typ", "vc+sd-jwt")
keyID?.also { put("kid", it) }
}.let { JSON.parse(it.toString()) }).sign(keyParam, options).then({
console.log("SIGNED")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ class SDJwtTestJVM {
val presentedUndisclosedJwt = sdJwt.present(discloseAll = false)
println("present without disclosing SD fields: " + presentedUndisclosedJwt)

//
// present disclosing all SD fields
val presentedDisclosedJwt = sdJwt.present(discloseAll = true)
println("present disclosing all SD fields: " + presentedDisclosedJwt)

Expand Down
10 changes: 10 additions & 0 deletions waltid-sdjwt/src/nativeInterop/cinterop/waltid-sd-jwt-ios.def
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
language = Objective-C
staticLibraries = libwaltid-sd-jwt-ios.a
libraryPaths.ios_simulator_arm64 = waltid-sd-jwt-ios/build/Release-iphonesimulator/
libraryPaths.ios_x64 = waltid-sd-jwt-ios/build/Release-iphonesimulator/
libraryPaths.ios_arm64 = waltid-sd-jwt-ios/build/Release-iphoneos/

linkerOpts = -L/usr/lib/swift
linkerOpts.ios_arm64 = -L/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/swift/iphoneos/
linkerOpts.ios_simulator_arm64 = -L/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/swift/iphonesimulator/
linkerOpts.ios_x64 = -L/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/swift/iphonesimulator/

0 comments on commit e50ec53

Please sign in to comment.