Skip to content
This repository has been archived by the owner on Dec 13, 2023. It is now read-only.

Commit

Permalink
Feat ios target (#11)
Browse files Browse the repository at this point in the history
  • Loading branch information
severinstampler authored Nov 16, 2023
2 parents dbe3719 + f979b46 commit 85c69ac
Show file tree
Hide file tree
Showing 17 changed files with 863 additions and 2 deletions.
59 changes: 57 additions & 2 deletions build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
plugins {
kotlin("multiplatform") version "1.9.20"

kotlin("plugin.serialization") version "1.9.0"
kotlin("plugin.serialization") version "1.9.20"


id("dev.petuska.npm.publish") version "3.4.1"
`maven-publish`
Expand All @@ -15,14 +16,16 @@ repositories {
mavenCentral()
}

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

js(IR) {
browser {
commonWebpackConfig(Action {
Expand Down Expand Up @@ -50,6 +53,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 +128,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
6 changes: 6 additions & 0 deletions gradle.properties
Original file line number Diff line number Diff line change
@@ -1,2 +1,8 @@
kotlin.code.style=official
kotlin.js.compiler=ir

kotlin.native.osVersionMin.ios_x64 = 13.0
kotlin.native.osVersionMin.ios_arm64 = 13.0
kotlin.native.cacheKind=none

kotlin.mpp.enableCInteropCommonization=true
1 change: 1 addition & 0 deletions settings.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
include(":waltid-sd-jwt-ios")
15 changes: 15 additions & 0 deletions 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() ?: "")
}
}
}
40 changes: 40 additions & 0 deletions src/iosMain/kotlin/id/walt/sdjwt/HMACJWTCryptoProvider.kt
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 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 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())
}
}
10 changes: 10 additions & 0 deletions 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/
28 changes: 28 additions & 0 deletions waltid-sd-jwt-ios/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
listOf("iphoneos", "iphonesimulator").forEach { sdk ->
tasks.register<Exec>("build${sdk.capitalize()}") {
group = "build"

commandLine(
"xcodebuild",
"-project", "waltid-sd-jwt-ios.xcodeproj",
"-scheme","waltid-sd-jwt-ios",
"-sdk", sdk,
"-configuration","Release"
)
workingDir(projectDir)

inputs.files(
fileTree("$projectDir/waltid-sd-jwt-ios.xcodeproj") { exclude("**/xcuserdata") },
fileTree("$projectDir/waltid-sd-jwt-ios")
)
outputs.files(
fileTree("$projectDir/build/Release-${sdk}")
)
}
}

tasks.create<Delete>("clean") {
group = "build"

delete("$projectDir/build")
}
Loading

0 comments on commit 85c69ac

Please sign in to comment.