Skip to content

Commit

Permalink
Merge pull request #18 from walt-id/sd-jwt
Browse files Browse the repository at this point in the history
feat: Sd jwt
  • Loading branch information
severinstampler authored Nov 20, 2023
2 parents 9cbe6bb + 29d5c92 commit 4401095
Show file tree
Hide file tree
Showing 128 changed files with 5,061 additions and 768 deletions.
20 changes: 10 additions & 10 deletions .run/Issuer.run.xml
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Issuer" type="JetRunConfigurationType">
<option name="MAIN_CLASS_NAME" value="id.walt.issuer.MainKt" />
<module name="waltid-identity.waltid-issuer.main" />
<option name="PROGRAM_PARAMETERS" value="--webHost=0.0.0.0 --webPort=7000 --baseUrl=http://dev.local:7000" />
<shortenClasspath name="NONE" />
<method v="2">
<option name="Make" enabled="true" />
</method>
</configuration>
</component>
<configuration default="false" name="Issuer" type="JetRunConfigurationType">
<option name="MAIN_CLASS_NAME" value="id.walt.issuer.MainKt"/>
<module name="waltid-identity.waltid-issuer.main"/>
<option name="PROGRAM_PARAMETERS" value="--webHost=0.0.0.0 --webPort=7000 --baseUrl=http://dev.local:7000"/>
<shortenClasspath name="NONE"/>
<method v="2">
<option name="Make" enabled="true"/>
</method>
</configuration>
</component>
20 changes: 10 additions & 10 deletions .run/Verifier.run.xml
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Verifier" type="JetRunConfigurationType">
<option name="MAIN_CLASS_NAME" value="id.walt.verifier.MainKt" />
<module name="waltid-identity.waltid-verifier.main" />
<option name="PROGRAM_PARAMETERS" value="--webHost=0.0.0.0 --webPort=7001 --baseUrl=http://dev.local:7001" />
<shortenClasspath name="NONE" />
<method v="2">
<option name="Make" enabled="true" />
</method>
</configuration>
</component>
<configuration default="false" name="Verifier" type="JetRunConfigurationType">
<option name="MAIN_CLASS_NAME" value="id.walt.verifier.MainKt"/>
<module name="waltid-identity.waltid-verifier.main"/>
<option name="PROGRAM_PARAMETERS" value="--webHost=0.0.0.0 --webPort=7001 --baseUrl=http://dev.local:7001"/>
<shortenClasspath name="NONE"/>
<method v="2">
<option name="Make" enabled="true"/>
</method>
</configuration>
</component>
13 changes: 10 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
## Supported

### Platforms available:

- Java / JVM
- JS / Node.js or WebCrypto
- Native / libsodium & OpenSSL (todo)
Expand All @@ -36,7 +37,9 @@
| RSA | RS256 |

### Compatibility matrix:

#### JWS (recommended)

| Algorithm | JVM provider | JS provider / platform |
|:---------:|:------------:|:---------------------------:|
| EdDSA | Nimbus JOSE | jose / Node.js |
Expand All @@ -45,6 +48,7 @@
| RS256 | Nimbus JOSE | jose / Node.js & Web Crypto |

#### LD Signatures (happy to add upon request - [email protected])

| Suite | JVM provider | JS provider |
|:---------------------------:|:------------------:|:-----------------:|
| Ed25519Signature2018 | ld-signatures-java | |
Expand All @@ -53,13 +57,13 @@
| RsaSignature2018 | ld-signatures-java | |
| JsonWebSignature2020 | ld-signatures-java | |



## Docker container builds:

```shell
docker build -t waltid/issuer -f docker/issuer.Dockerfile .
docker run -p 7000:7000 waltid/issuer --webHost=0.0.0.0 --webPort=7000 --baseUrl=http://localhost:7000
```

```shell
docker build -t waltid/verifier -f docker/verifier.Dockerfile .
docker run -p 7001:7001 waltid/verifier --webHost=0.0.0.0 --webPort=7001 --baseUrl=http://localhost:7001
Expand All @@ -68,9 +72,12 @@ docker run -p 7001:7001 waltid/verifier --webHost=0.0.0.0 --webPort=7001 --baseU
### Setup Vault

#### Download

wget -O- https://apt.releases.hashicorp.com/gpg | sudo gpg --dearmor -o /usr/share/keyrings/hashicorp-archive-keyring.gpg
echo "deb [signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] https://apt.releases.hashicorp.com $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/hashicorp.list
echo "deb [signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] https://apt.releases.hashicorp.com $(lsb_release -cs) main" | sudo
tee /etc/apt/sources.list.d/hashicorp.list
sudo apt update && sudo apt install vault

#### Run Vault in Dev mode

vault server -dev -dev-root-token-id="dev-only-token"
2 changes: 1 addition & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ plugins {
val kotlinVersion = "1.9.20"
kotlin("multiplatform") version kotlinVersion apply false
kotlin("plugin.serialization") version kotlinVersion apply false
id("com.github.ben-manes.versions") version "0.48.0" apply false
id("com.github.ben-manes.versions") version "0.49.0" apply false
kotlin("jvm") version "1.9.20"
}
dependencies {
Expand Down
1 change: 1 addition & 0 deletions settings.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ include(
"waltid-crypto",
"waltid-did",
"waltid-verifiable-credentials",
"waltid-sdjwt",

// Protocols
"waltid-openid4vc",
Expand Down
2 changes: 1 addition & 1 deletion waltid-crypto/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ kotlin {
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.0")
}
}
publishing {
publishing {
repositories {
maven {
url = uri("https://maven.walt.id/repository/waltid/")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ abstract class Key {
* @return raw signature
*/
abstract suspend fun signRaw(plaintext: ByteArray): Any

/**
* signs a message using this private key (with the algorithm this key is based on)
* @exception IllegalArgumentException when this is not a private key
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,9 @@ object KeySerialization {
}
}

private val keySerializationJson = Json { serializersModule =
keySerializationModule
private val keySerializationJson = Json {
serializersModule =
keySerializationModule
}

fun serializeKey(key: Key): String = keySerializationJson.encodeToString(key)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,6 @@ expect class LocalKey(jwk: String?) : Key {
override val hasPrivateKey: Boolean



companion object : LocalKeyCreator {

override suspend fun generate(type: KeyType, metadata: LocalKeyMetadata): LocalKey
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import kotlin.io.encoding.ExperimentalEncodingApi
@OptIn(ExperimentalEncodingApi::class)
object Base64Utils {

fun String.base64toBase64Url() = this.replace("+", "-").replace("/", "_").dropLastWhile { it == '=' }
fun String.base64toBase64Url() = this.replace("+", "-").replace("/", "_").dropLastWhile { it == '=' }
fun String.base64UrlToBase64() = this.replace("-", "+").replace("_", "/")

fun ByteArray.encodeToBase64Url() = Base64.UrlSafe.encode(this).dropLastWhile { it == '=' }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ object JsonUtils {
return JsonObject(map)
}

fun Map<*, *>.toJsonObject() = this.toJsonElement().jsonObject

private fun toHexChar(i: Int): Char {
val d = i and 0xf
return if (d < 10) (d + '0'.code).toChar()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,17 +21,22 @@ object JwsUtils {
fun String.decodeJwsPart(): JsonObject =
Json.parseToJsonElement(Base64.decode(this.base64UrlToBase64()).decodeToString()).jsonObject

data class JwsParts(val header: JsonObject, val payload: JsonObject)
data class JwsParts(val header: JsonObject, val payload: JsonObject, val signature: String)

fun String.decodeJws(): JwsParts {
fun String.decodeJws(withSignature: Boolean = false): JwsParts {
check(startsWith("ey")) { "String does not look like JWS: $this" }
check(count { it == '.' } == 2) { "String does not have JWS part amount of 3 (= 2 dots): $this" }

val splitted = split(".")
val header = runCatching { splitted[0].decodeJwsPart() }.getOrElse { throw IllegalArgumentException("Could not parse JWT header (base64/json issue): ${splitted[0]}", it) }
val payload = runCatching { splitted[1].decodeJwsPart() }.getOrElse { throw IllegalArgumentException("Could not parse JWT payload (base64/json issue): ${splitted[1]}", it) }

return JwsParts(header, payload)
val header = runCatching { splitted[0].decodeJwsPart() }.getOrElse { ex ->
throw IllegalArgumentException("Could not parse JWT header (base64/json issue): ${splitted[0]}", ex)
}
val payload = runCatching { splitted[1].decodeJwsPart() }.getOrElse { ex ->
throw IllegalArgumentException("Could not parse JWT payload (base64/json issue): ${splitted[1]}", ex)
}
val signature = if (withSignature) splitted[2] else ""

return JwsParts(header, payload, signature)
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ object MultiCodecUtils {
KeyType.RSA -> 0x1205u
}

fun getKeyTypeFromKeyCode(code: UInt): KeyType = when (code){
fun getKeyTypeFromKeyCode(code: UInt): KeyType = when (code) {
0xEDu -> KeyType.Ed25519
0xE7u -> KeyType.secp256k1
0x1205u -> KeyType.RSA
Expand Down Expand Up @@ -74,4 +74,4 @@ object MultiCodecUtils {
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@ fun main() {
val privateKeyBase64Url = Base64.UrlSafe.encode(privateKey).dropLastWhile { it == '=' }
val publicKeyBase64Url = Base64.UrlSafe.encode(publicKey).dropLastWhile { it == '=' }

val jwk = """{"kty":"OKP","d":"$privateKeyBase64Url","use":"sig","crv":"Ed25519","kid":"k1","x":"$publicKeyBase64Url","alg":"EdDSA"}""".trimIndent()
val jwk =
"""{"kty":"OKP","d":"$privateKeyBase64Url","use":"sig","crv":"Ed25519","kid":"k1","x":"$publicKeyBase64Url","alg":"EdDSA"}""".trimIndent()

println(jwk)
}
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,9 @@ actual class LocalKey actual constructor(
}

actual companion object : LocalKeyCreator {
actual override suspend fun generate(type: KeyType, metadata: LocalKeyMetadata): LocalKey = JvmLocalKeyCreator.generate(type, metadata)
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)
actual override suspend fun importPEM(pem: String): Result<LocalKey> = JvmLocalKeyCreator.importPEM(pem)
actual override suspend fun importRawPublicKey(
Expand Down
19 changes: 13 additions & 6 deletions waltid-did/readme.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<div align="center">
<h1>walt.id did-lib - Kotlin multiplatform library</h1>
<span>by </span><a href="https://walt.id">walt.id</a>

[![CI/CD Workflow for walt.id did]()]()
<a href="https://walt.id/community">
<img src="https://img.shields.io/badge/Join-The Community-blue.svg?style=flat" alt="Join community!" />
Expand All @@ -17,14 +17,16 @@

_**walt.id did**_ library provides functionality for registering and resolving DIDs.
There are 2 options offered for each function:

- universal - relies on the universal DID registrar / resolver, e.g.:
- uni-registrar - https://uniregistrar.io
- uni-resolver - https://dev.uniresolver.io
- uni-registrar - https://uniregistrar.io
- uni-resolver - https://dev.uniresolver.io
- local - provides local implementations of DID methods

For the cryptographic part, _**walt.id did**_ library relies on _**walt.id crypto**_ library.

## Class diagram

![walt.id did class diagram](did-lib_class.drawio.png)

The top-level interface to access the registrar / resolver functions is provided
Expand All @@ -33,7 +35,9 @@ by the `DidService` singleton.
## Usage examples

### Register DID

Create the key and register the Did:

```kotlin
val options = DidWebCreateOptions(
domain = "localhost:3000",
Expand All @@ -44,6 +48,7 @@ val didResult = DidService.register(options = options)
```

Register the Did with the given key:

```kotlin
val key = LocalKey.generate(KeyType.Ed25519)
val options = DidKeyCreateOptions(
Expand All @@ -57,14 +62,16 @@ val didResult = DidService.register(
```

Both calls return a `DidResult` object:

```kotlin
data class DidResult(
val did: String,
val didDocument: DidDocument
)
```

where `did` - is the Did url string, while `didDocument` is the corresponding
DidDocument represented as a key-value pair, having the key as a `String` and
DidDocument represented as a key-value pair, having the key as a `String` and
value as a `JsonElement`.

Currently available local did methods are:
Expand Down Expand Up @@ -96,5 +103,5 @@ Both calls return the result using the _operation result pattern_,
the data being wrapped by the `Result` object. This allows checking for
a successful operation and handling the result accordingly.

The Did Document data is represented as `JsonObject`. The key data is
represented as **_walt.id crypto_** `Key`.
The Did Document data is represented as `JsonObject`. The key data is
represented as **_walt.id crypto_** `Key`.
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,11 @@ object DidService {
registerRegistrarForMethod(method, registrar)
}

else -> log.warn { "DID Registrar ${registrar.name} cannot be used, error: ${methods.exceptionOrNull().let { it?.message ?: it.toString() }}" }
else -> log.warn {
"DID Registrar ${registrar.name} cannot be used, error: ${
methods.exceptionOrNull().let { it?.message ?: it.toString() }
}"
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ class LocalRegistrar : DidRegistrar {

override suspend fun create(options: DidCreateOptions): DidResult =
getRegistrarForMethod(options.method).register(options)

override suspend fun createByKey(key: Key, options: DidCreateOptions): DidResult =
getRegistrarForMethod(options.method).registerByKey(key, options)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,18 @@ open class DidCreateOptions(val method: String, val options: JsonElement) {

constructor(method: String, options: Map<String, Any?>) : this(method, options.toJsonElement())

inline operator fun <reified T> get(name: String): T? = options.jsonObject["options"]?.jsonObject?.get(name)?.jsonPrimitive?.content?.let {
when (T::class) {
Boolean::class -> it.toBoolean()
Int::class -> it.toIntOrNull()
Long::class -> it.toLongOrNull()
Double::class -> it.toDoubleOrNull()
KeyType::class -> enumValueIgnoreCase<KeyType>(it)
String::class -> it
else -> null
} as? T
}
inline operator fun <reified T> get(name: String): T? =
options.jsonObject["options"]?.jsonObject?.get(name)?.jsonPrimitive?.content?.let {
when (T::class) {
Boolean::class -> it.toBoolean()
Int::class -> it.toIntOrNull()
Long::class -> it.toLongOrNull()
Double::class -> it.toDoubleOrNull()
KeyType::class -> enumValueIgnoreCase<KeyType>(it)
String::class -> it
else -> null
} as? T
}
}

internal fun options(options: Map<String, Any>, secret: Map<String, Any> = emptyMap()) = mapOf(
Expand All @@ -33,4 +34,5 @@ internal fun options(options: Map<String, Any>, secret: Map<String, Any> = empty
),
"secret" to secret
)

internal fun options(vararg inlineOptions: Pair<String, Any>) = options(mapOf(*inlineOptions))
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ class DidCheqdRegistrar() : LocalRegistrarMethod("cheqd") {
isLenient = true
explicitNulls = false
}

//TODO: inject
private val client = HttpClient(CIO) {
install(ContentNegotiation) {
Expand Down
4 changes: 2 additions & 2 deletions waltid-did/src/jvmTest/kotlin/DidDocumentChecks.kt
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ object DidDocumentChecks {
* Checks [actual] and [expected] **y** is identical
* @return True if all checks pass, False otherwise
*/
fun secp256KeyChecks(actual: JsonObject, expected: JsonObject)= let{
fun secp256KeyChecks(actual: JsonObject, expected: JsonObject) = let {
actual["y"]!!.jsonPrimitive.content == expected["y"]!!.jsonPrimitive.content
}

Expand All @@ -75,4 +75,4 @@ object DidDocumentChecks {
&& actual["e"]!!.jsonPrimitive.content.equals(expected["e"]!!.jsonPrimitive.content, true)
}
//endregion -DidDocument vs. key-
}
}
Loading

0 comments on commit 4401095

Please sign in to comment.