diff --git a/.run/Web wallet frontend (dev).run.xml b/.run/Web wallet frontend (dev).run.xml index d5377fc07..ea7f2cb83 100644 --- a/.run/Web wallet frontend (dev).run.xml +++ b/.run/Web wallet frontend (dev).run.xml @@ -10,4 +10,4 @@ - \ No newline at end of file + diff --git a/build.gradle.kts b/build.gradle.kts index e10cc9bb7..eb3fb25a3 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,3 +1,5 @@ +import com.github.benmanes.gradle.versions.updates.DependencyUpdatesTask + allprojects { version = "1.0.0-SNAPSHOT" @@ -23,3 +25,11 @@ repositories { kotlin { jvmToolchain(8) } + +allprojects { + tasks.withType { + rejectVersionIf { + listOf("-beta", "-alpha", "-rc").any { it in candidate.version.lowercase() } || candidate.version.takeLast(4).contains("RC") + } + } +} diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 1af9e0930..a80b22ce5 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/gradlew.bat b/gradlew.bat index 93e3f59f1..25da30dbd 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -43,11 +43,11 @@ set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if %ERRORLEVEL% equ 0 goto execute -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail @@ -57,11 +57,11 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto execute -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail diff --git a/waltid-cli/README.md b/waltid-cli/README.md index 1a3836313..9f5077358 100644 --- a/waltid-cli/README.md +++ b/waltid-cli/README.md @@ -1 +1,229 @@ # walt.id CLI + +Manage keys, DIDs, issue Verifiable Credentials, and verify them using the WaltId command line tool. + +# How to use + +## In development + +* `git clone https://github.com/walt-id/waltid-identity.git` +* `cd waltid-identity/waltid-cli` +* `../gradlew clean build` +* `alias waltid="./waltid-cli-development.sh"` + +Now, you can run: + +| Command | What it does | +|:------------------------------------------------------------------------------------|:------------------------------------------------------------------------------------------------------------------------| +| `waltid -h` | Print usage message | +| `waltid --help` | Print WaltId CLI usage message | +| `waltid key -h` | Print WaltId CLI key command usage message | +| `waltid key generate -h` | Print WaltId CLI key generate command usage message | +| `waltid key convert -h` | Print WaltId CLI key generate command usage message | +| `waltid key generate` | Generates a cryptographic key of type Ed25519 | +| `waltid key generate -tsecp256k1` | Generates a cryptographic key of type secp256k1 | +| `waltid key generate --keyType=RSA` | Generates a cryptographic key of type RSA | +| `waltid key generate --keyType=RSA -omyRSAKey.json` | Generates a cryptographic key of type RSA and save it in a file called myRSAKey.json | +| `waltid key generate --keyType=Ed25519 -o myEd25519Key.json` | Generates a cryptographic key of type Ed25519 and save it in a file called myEd25519.json | +| `waltid key generate --keyType=secp256k1 -o mySecp256k1Key.json` | Generates a cryptographic key of type Secp256k1 and save it in a file called mySecp256k1.json | +| `waltid key generate --keyType=secp256r1 -o mySecp256r1Key.json` | Generates a cruyptographic key of type Secp256r1 and save it in a file called mySecp256r1.json | +| `waltid key convert -i myRSAKey.json` | Convert the given JWK file with an RSA key to the PEM format. The converted file will be called myRSA.pem | +| `waltid key convert -i myEd25519Key.json` | ⚠️ Not yet implemented. We don't export Ed25519 keys in PEM format yet. | +| `waltid key convert -i mySecp256k1Key.json` | Convert the given JWK with a Secp256k1 key file to the PEM format. The converted file will be called mySecp256k1Key.pem | +| `waltid key convert -i mySecp256r1Key.json` | Convert the given JWK with a Secp256r1 key file to the PEM format. The converted file will be called mySecp256r1Key.pem | +| `waltid key convert --input=./myRSAKey.pem` | ⚠️ Not yet implemented. | +| `waltid key convert --input=./myEd25519Key.pem` | ⚠️ Not yet implemented. | +| `waltid key convert --input=./mySecp256k1Key.pem` | Converts the given PEM with a Sec256k1 key to the JWK format. The converted file will be called mySecp256k1Key.jwk | +| `waltid key convert --input=./mySecp256r1Key.pem --output=./convertedSecp256r1.jwk` | Converts the given PEM with a Sec256r1 key to the JWK format. The converted file will be called convertedSecp256r1.jwk | +| `openssl ecparam -genkey -name secp256k1 -out secp256k1_by_openssl_pub_pvt_key.pem` | Uses OpenSSL to generate a pair of keys in a PEM file. | | +| `waltid key convert --verbose -i secp256k1_by_openssl_pub_pvt_key.pem` | Converts the Secp256k1 key in the given PEM file to the JWK format. | + +## In production + +We are still preparing a nice distribution strategy. It will be available soon. + +In the meantime, you can use Gradle to generate the distribution package: + +* `cd waltid-identity/waltid-cli` +* `../gradlew distZip` or `../gradlew distTar` + +A `waltid-cli-1.0.0-SNAPSHOT` file will be created in the `build/distributions` directory. + +```bash +$ pwd +.../waltid-identity/waltid-cli + +$ ls -la build/distributions/ +total 67024 +drwxr-xr-x@ 3 waltian staff 96 Feb 20 18:41 . +drwxr-xr-x@ 12 waltian staff 384 Feb 20 18:41 .. +-rw-r--r--@ 1 waltian staff 34062716 Feb 20 18:41 waltid-cli-1.0.0-SNAPSHOT.zip +``` + +Extract it somewhere and you will find two folders inside: + +```bash +$ unzip build/distributions/waltid-cli-1.0.0-SNAPSHOT.zip -d /tmp +(...) + +$ ll /tmp/waltid-cli-1.0.0-SNAPSHOT +total 0 +drwxr-xr-x@ 4 waltian wheel 128B Feb 20 18:41 bin +drwxr-xr-x@ 72 waltian wheel 2.3K Feb 20 18:41 lib +``` + +The `bin` folder has the CLI execution script compatible with common operating systems. + +Set execution rights to the script + +`$ chmod a+x /tmp/waltid-cli-1.0.0-SNAPSHOT/bin/waltid` + +Add the `/tmp/waltid-cli-1.0.0-SNAPSHOT/bin` to the PATH. + +`$ export PATH="/tmp/waltid-cli-1.0.0-SNAPSHOT/bin:$PATH"` + +Execute the walt.id CLI + +`$ waltid` + +# Supported commands + +| Command | Subommand | Description | Ready to use | +|:-------:|:---------:|------------------------------------------------|:------------:| +| key | generate | Generates a new cryptographic key. | ✔️ | +| | convert | Convert key files between PEM and JWK formats. | | +| | |
  • from PEM to JWK
  • | ✔️ | +| | |
  • Convertion from JWK to PEM
  • | ✖️ | +| did | create | Create a new DID. | ✖️ | +| | resolve | Resolve a DID. | ✖️ | +| vc | issue | Issue a verifiable credential | ✖️ | +| | verify | Verify a verifiable credential | ✖️ | +| | present | Present a verifiable credential | ✖️ | +| | ... | | | + +# Reference + +## `waltid` command + +```bash +Usage: waltid [] []... + + WaltId CLI + + ╭─────────────────────────────────────────────────────────────────────────╮ + │ The walt.id CLI is a command line tool that allows you to onboard and│ + │ use a SSI (Self-Sovereign-Identity) ecosystem. You can manage │ + │ cryptographic keys, generate and register W3C Decentralized │ + │ Identifiers (DIDs) as well as create, issue & verify W3C Verifiable │ + │ credentials (VCs). │ + │ │ + │ Example commands are: │ + │ │ + │ Print usage instructions │ + │ ------------------------- │ + │ waltid -h │ + │ waltid --help │ + │ waltid key -h │ + │ waltid key generate -h │ + │ waltid key convert -h │ + │ │ + │ Key generation │ + │ --------------- │ + │ waltid key generate │ + │ waltid key generate -t secp256k1 │ + │ waltid key generate --keyType=RSA │ + │ waltid key generate --keyType=RSA -o myRsaKey.json │ + │ │ + │ Key conversion │ + │ --------------- │ + │ waltid key convert --input=myRsaKey.pem │ + ╰─────────────────────────────────────────────────────────────────────────╯ + +Options: + -h, --help Show this message and exit + +Commands: + key Key management features +``` + +## `waltid key` command + +```bash +Usage: waltid key [] []... + + Key management features + +Options: + -h, --help Show this message and exit + +Commands: + generate Generates a new cryptographic key. + convert Convert key files between PEM and JWK formats. +``` + +## `waltid key generate` command + +```bash +Usage: waltid key generate [] + + Generates a new cryptographic key. + +Options: + -t, --keyType=(Ed25519|secp256k1|secp256r1|RSA) + Key type to use. Possible values are: [Ed25519 | + secp256k1 | secp256r1 | RSA]. Default value is Ed25519 + -o, --output= File path to save the generated key. Default value is + .json + -h, --help Show this message and exit +``` + +## `waltid key convert` command + +```bash +Usage: waltid key convert [] + + Convert key files between PEM and JWK formats. + +Options: + -i, --input= The input file path. Accepted formats are: JWK and + PEM + -o, --output= The output file path. Accepted formats are: JWK and + PEM. If not provided the input filename will be used + with a different extension. + -p, --passphrase= Passphrase to open an encrypted PEM + -h, --help Show this message and exit +``` + +# Compatibility + +This project is still a work in progress. As such, not all features are already implemented. + +## Key Management + +### key generate + +* Supported key types + * Ed25519 ✅ + * secp256k1 ✅ + * secp256r1 ✅ + * RSA ✅ +* Export formats + * JWK ✅ + * PEM ❌ + +### `key convert + +* Input formats + * PEM ✅ + * JWK ✅ +* Output formats + * PEM ❌ + * JWK ✅ +* PEM Content + * RSA Private Key ✅ + * RSA Public Key ✅ + * RSA Encrypted Private Key ✅ + * Ed25519 ❌ + * secp256k1 ❌ + * secp256r1 ❌ + diff --git a/waltid-cli/build.gradle.kts b/waltid-cli/build.gradle.kts index 9ed81ade4..003ce7dad 100644 --- a/waltid-cli/build.gradle.kts +++ b/waltid-cli/build.gradle.kts @@ -5,6 +5,8 @@ plugins { kotlin("plugin.serialization") id("maven-publish") id("com.github.ben-manes.versions") + // Apply the application plugin to add support for building a CLI application in Java. + application } group = "id.walt.cli" @@ -43,6 +45,9 @@ kotlin { api(project(":waltid-sdjwt")) api(project(":waltid-openid4vc")) + // kotlinx-io + implementation("org.jetbrains.kotlinx:kotlinx-io-core:0.3.1") + // CLI implementation("com.varabyte.kotter:kotter-jvm:1.1.2") implementation("com.github.ajalt.mordant:mordant:2.3.0") @@ -65,12 +70,18 @@ kotlin { dependencies { // Logging implementation("org.slf4j:slf4j-simple:2.0.12") + + // JOSE + implementation("com.nimbusds:nimbus-jose-jwt:9.37.3") + + // BouncyCastle for PEM import + implementation("org.bouncycastle:bcpkix-lts8on:2.73.4") } } val jvmTest by getting { dependencies { implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.3") - implementation("org.junit.jupiter:junit-jupiter-params:5.10.2") + implementation("com.wolpl.clikt-testkit:clikt-testkit:2.0.0") } } /*publishing { @@ -104,9 +115,14 @@ kotlin { } } +application { + // Define the main class for the application. + // Works with: + // ../gradlew run --args="--help" + mainClass = "id.walt.cli.MainKt" -tasks.withType { - rejectVersionIf { - listOf("-beta", "-alpha", "-rc").any { it in candidate.version.lowercase() } || candidate.version.takeLast(4).contains("RC") - } +} + +tasks.test { + useJUnitPlatform() } diff --git a/waltid-cli/src/commonMain/kotlin/id/walt/cli/Main.kt b/waltid-cli/src/commonMain/kotlin/id/walt/cli/Main.kt index d3dde7a0c..e6221183b 100644 --- a/waltid-cli/src/commonMain/kotlin/id/walt/cli/Main.kt +++ b/waltid-cli/src/commonMain/kotlin/id/walt/cli/Main.kt @@ -1,24 +1,77 @@ package id.walt.cli -import com.github.ajalt.clikt.core.subcommands -import id.walt.cli.commands.Did +import com.github.ajalt.clikt.core.* +import com.github.ajalt.clikt.output.ParameterFormatter +import com.github.ajalt.mordant.rendering.TextColors +import com.github.ajalt.mordant.rendering.Whitespace +import com.github.ajalt.mordant.widgets.Panel +import com.github.ajalt.mordant.widgets.Text +import kotlin.system.exitProcess fun main(args: Array) { - println("-- walt.id SSI Kit v2 CLI --") + val cmd = WaltIdCmd() + try { + cmd.parse(args) + } catch (e: PrintHelpMessage) { + cmd.echoFormattedHelp(e) + exitProcess(e.statusCode) + } catch (e: InvalidFileFormat) { + printError(cmd, e) + printUsage(cmd, e) + exitProcess(e.statusCode) + } catch (e: MultiUsageError) { + var msgs = "Invalid command. Please, review the usage instructions bellow and try again." + // for (error in e.errors) { + // if (msgs.length == 0) { + // // msgs = error.formatMessage(error.context!!.localization, parameterFormatter(error.context!!)) + // msgs = "${error.localizedMessage} - ${error.message} " + // } else { + // msgs = """${msgs} ${error.toString() ?: ""}""" + // } + // } + printError(cmd, e, msgs) + printUsage(cmd, e) - MainCommand() - .subcommands( - Did().subcommands( - Did.Create() - ) + } catch (e: CliktError) { + printError(cmd, e) + printUsage(cmd, e) + exitProcess(e.statusCode) + } +} + +fun printError(cmd: CliktCommand, e: CliktError? = null, msg: String? = null) { + println("\n") + val msgToPrint = msg ?: e?.let { it.localizedMessage } + cmd.terminal.println( + Panel( + content = Text(TextColors.brightRed(msgToPrint!!), whitespace = Whitespace.NORMAL, width = 70), + title = Text(TextColors.red("ERROR")) ) - .main(args) + ) + println("\n") +} + +fun printUsage(cmd: CliktCommand, e: CliktError) { + val ctx = (e as ContextCliktError).context + cmd.echoFormattedHelp(PrintHelpMessage(ctx)) +} + +fun parameterFormatter(context: Context): ParameterFormatter { + return object : ParameterFormatter { + override fun formatOption(name: String): String { + // return styleOptionName(name) + return context.theme.style("info")(name) + } + override fun formatArgument(name: String): String { + // return styleArgumentName(normalizeParameter(name)) + return context.theme.style("info")("<${name.lowercase()}>") + } - /*WaltidServices.init() - WaltidServices.minimalInit() + override fun formatSubcommand(name: String): String { + // return styleSubcommandName(name) + return context.theme.style("info")(name) + } - val dr = DidService.register(DidWebCreateOptions("localhost:3000")) - println(dr.did) - println(dr.didDocument)*/ + } } diff --git a/waltid-cli/src/commonMain/kotlin/id/walt/cli/MainCommand.kt b/waltid-cli/src/commonMain/kotlin/id/walt/cli/MainCommand.kt deleted file mode 100644 index c3742574a..000000000 --- a/waltid-cli/src/commonMain/kotlin/id/walt/cli/MainCommand.kt +++ /dev/null @@ -1,7 +0,0 @@ -package id.walt.cli - -import com.github.ajalt.clikt.core.CliktCommand - -class MainCommand() : CliktCommand() { - override fun run() = Unit -} diff --git a/waltid-cli/src/commonMain/kotlin/id/walt/cli/WaltIdCmd.kt b/waltid-cli/src/commonMain/kotlin/id/walt/cli/WaltIdCmd.kt new file mode 100644 index 000000000..b6c34a039 --- /dev/null +++ b/waltid-cli/src/commonMain/kotlin/id/walt/cli/WaltIdCmd.kt @@ -0,0 +1,49 @@ +package id.walt.cli + +import com.github.ajalt.clikt.core.CliktCommand +import com.github.ajalt.clikt.core.subcommands +import com.github.ajalt.clikt.parameters.groups.provideDelegate +import id.walt.cli.commands.CommonOptions +import id.walt.cli.commands.KeyCmd + +class WaltIdCmd : CliktCommand( + name = "waltid", + help = """walt.id CLI + + The walt.id CLI is a command line tool that allows you to onboard and + use a SSI (Self-Sovereign-Identity) ecosystem. You can manage + cryptographic keys, generate and register W3C Decentralized + Identifiers (DIDs) as well as create, issue & verify W3C Verifiable + credentials (VCs). + + Example commands are: + + Print usage instructions + ------------------------- + waltid -h + waltid --help + waltid key -h + waltid key generate -h + waltid key convert -h + + Key generation + --------------- + waltid key generate + waltid key generate -t secp256k1 + waltid key generate --keyType=RSA + waltid key generate --keyType=RSA -o myRsaKey.json + + Key conversion + --------------- + waltid key convert --input=myRsaKey.pem + """, + printHelpOnEmptyArgs = true +) { + init { + subcommands(KeyCmd()) + } + + private val commonOptions by CommonOptions() + + override fun run() = Unit +} diff --git a/waltid-cli/src/commonMain/kotlin/id/walt/cli/commands/CommandConfigUtils.kt b/waltid-cli/src/commonMain/kotlin/id/walt/cli/commands/CommandConfigUtils.kt index 3ff78145e..0e17e0b22 100644 --- a/waltid-cli/src/commonMain/kotlin/id/walt/cli/commands/CommandConfigUtils.kt +++ b/waltid-cli/src/commonMain/kotlin/id/walt/cli/commands/CommandConfigUtils.kt @@ -4,5 +4,7 @@ import kotlin.reflect.KClass object CommandConfigUtils { operator fun Map.get(key: KClass<*>): String = this[key.simpleName]!! - operator fun MutableMap.set(key: KClass<*>, value: String) { this[key.simpleName!!] = value } + operator fun MutableMap.set(key: KClass<*>, value: String) { + this[key.simpleName!!] = value + } } diff --git a/waltid-cli/src/commonMain/kotlin/id/walt/cli/commands/CommonOptions.kt b/waltid-cli/src/commonMain/kotlin/id/walt/cli/commands/CommonOptions.kt new file mode 100644 index 000000000..1a1167a3a --- /dev/null +++ b/waltid-cli/src/commonMain/kotlin/id/walt/cli/commands/CommonOptions.kt @@ -0,0 +1,11 @@ +package id.walt.cli.commands + +import com.github.ajalt.clikt.parameters.groups.OptionGroup +import com.github.ajalt.clikt.parameters.options.flag +import com.github.ajalt.clikt.parameters.options.help +import com.github.ajalt.clikt.parameters.options.option + +class CommonOptions : OptionGroup("Common Options") { + val verbose by option().flag().help("Set verbose mode ON") + +} \ No newline at end of file diff --git a/waltid-cli/src/commonMain/kotlin/id/walt/cli/commands/Did.kt b/waltid-cli/src/commonMain/kotlin/id/walt/cli/commands/Did.kt index beaa37260..a5fac3768 100644 --- a/waltid-cli/src/commonMain/kotlin/id/walt/cli/commands/Did.kt +++ b/waltid-cli/src/commonMain/kotlin/id/walt/cli/commands/Did.kt @@ -2,14 +2,9 @@ package id.walt.cli.commands import com.github.ajalt.clikt.completion.CompletionCandidates import com.github.ajalt.clikt.core.CliktCommand -import com.github.ajalt.clikt.core.findOrSetObject -import com.github.ajalt.clikt.core.requireObject -import com.github.ajalt.clikt.output.TermUi import com.github.ajalt.clikt.parameters.options.default import com.github.ajalt.clikt.parameters.options.option import com.github.ajalt.clikt.parameters.types.enum -import id.walt.cli.commands.CommandConfigUtils.get -import id.walt.cli.commands.CommandConfigUtils.set import id.walt.did.dids.DidService import kotlinx.coroutines.runBlocking diff --git a/waltid-cli/src/commonMain/kotlin/id/walt/cli/commands/KeyCmd.kt b/waltid-cli/src/commonMain/kotlin/id/walt/cli/commands/KeyCmd.kt new file mode 100644 index 000000000..ceb345455 --- /dev/null +++ b/waltid-cli/src/commonMain/kotlin/id/walt/cli/commands/KeyCmd.kt @@ -0,0 +1,22 @@ +package id.walt.cli.commands + +import com.github.ajalt.clikt.core.CliktCommand +import com.github.ajalt.clikt.core.subcommands +import com.github.ajalt.clikt.parameters.groups.provideDelegate + +class KeyCmd : CliktCommand( + name = "key", + help = "Key management features", + printHelpOnEmptyArgs = true +) { + + init { + subcommands(KeyGenerateCmd(), KeyConvertCmd()) + } + + private val commonOptions by CommonOptions() + + override fun run() = Unit +} + +fun main(args: Array) = KeyCmd().main(args) diff --git a/waltid-cli/src/commonMain/kotlin/id/walt/cli/commands/KeyConvertCmd.kt b/waltid-cli/src/commonMain/kotlin/id/walt/cli/commands/KeyConvertCmd.kt new file mode 100644 index 000000000..2b2a8e0c8 --- /dev/null +++ b/waltid-cli/src/commonMain/kotlin/id/walt/cli/commands/KeyConvertCmd.kt @@ -0,0 +1,231 @@ +package id.walt.cli.commands + +import com.github.ajalt.clikt.core.* +import com.github.ajalt.clikt.parameters.groups.provideDelegate +import com.github.ajalt.clikt.parameters.options.defaultLazy +import com.github.ajalt.clikt.parameters.options.help +import com.github.ajalt.clikt.parameters.options.option +import com.github.ajalt.clikt.parameters.options.required +import com.github.ajalt.clikt.parameters.types.file +import com.github.ajalt.mordant.markdown.Markdown +import com.github.ajalt.mordant.rendering.TextColors +import com.github.ajalt.mordant.rendering.TextStyles +import com.github.ajalt.mordant.terminal.YesNoPrompt +import id.walt.crypto.keys.Key +import id.walt.crypto.keys.LocalKey +import kotlinx.coroutines.runBlocking +import org.bouncycastle.jce.provider.BouncyCastleProvider +import org.bouncycastle.openssl.PEMEncryptedKeyPair +import org.bouncycastle.openssl.PEMParser +import org.bouncycastle.openssl.bc.BcPEMDecryptorProvider +import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter +import org.bouncycastle.pkcs.PKCS8EncryptedPrivateKeyInfo +import org.bouncycastle.pkcs.PKCSException +import org.bouncycastle.pkcs.jcajce.JcePKCSPBEInputDecryptorProviderBuilder +import java.io.File +import java.io.FileReader +import java.security.PrivateKey +import java.security.Security +import java.util.* +import kotlin.io.path.useLines +import kotlin.js.ExperimentalJsExport + + +enum class KeyFileFormat { + JWK, PEM, ENCRYPTED_PEM; + + companion object { + fun getNames() = KeyFileFormat.entries.joinToString { it.name } + } +} + +@OptIn(ExperimentalJsExport::class) +class KeyConvertCmd : CliktCommand( + name = "convert", help = "Convert key files between PEM and JWK formats.", printHelpOnEmptyArgs = true +) { + + private val input by option("-i", "--input").help("The input file path. Accepted formats are: JWK and PEM") + .file(mustExist = true, canBeFile = true, canBeDir = false, mustBeReadable = true).required() + + + private val sourceKeyType by lazy { getKeyType(input) } + private val targetKeyType by lazy { getOutputKeyType(sourceKeyType) } + + private val output by option( + "-o", + "--output" + ).help("The output file path. Accepted formats are: JWK and PEM. If not provided the input filename will be used with a different extension.") + .file().defaultLazy { + File(input.parent, "${input.nameWithoutExtension}.${targetKeyType.toString().lowercase()}") + } + + private val passphrase by option("-p", "--passphrase").help("Passphrase to open an encrypted PEM") + // .prompt(text = "Please, inform the PEM passphrase", hideInput = true) + + private val commonOptions by CommonOptions() + + override fun run() { + // Read the source key from the input file + echo(TextStyles.dim("Reading key \"${input.absolutePath}\"...")) + val inputKey = runBlocking { getKey(input) } + + echo(TextStyles.dim("Converting key \"${input.absolutePath}\"...")) + val outputContent = runBlocking { convertKey(inputKey, targetKeyType) } + + echo(TextColors.green("Converted Key (${targetKeyType}):")) + terminal.println( + Markdown( + """ + |```${if (targetKeyType == KeyFileFormat.JWK) "json" else ""} + |$outputContent + |``` + """.trimMargin() + ) + ) + + if (output.exists() + && YesNoPrompt( + "The file \"${output.absolutePath}\" already exists, do you want to overwrite it?", + terminal + ).ask() == false + ) { + echo("Will not overwrite output file.") + return + } + + output.writeText(outputContent) + + echo("${TextColors.brightGreen("Done.")} Converted \"${input.absolutePath}\" to \"${output.absolutePath}\".") + } + + private operator fun Regex.contains(text: CharSequence?): Boolean = this.matches(text ?: "") + private fun getKeyType(input: File): KeyFileFormat = input.toPath().useLines { lines -> + when (lines.firstOrNull()) { + null -> throw InvalidFileFormat(input.path, "No lines in file.") + + // other PEM content types: https://github.com/openssl/openssl/blob/master/include/openssl/pem.h + in Regex("""^\{.*""") -> KeyFileFormat.JWK + in Regex("""^-+BEGIN .*PUBLIC KEY-+""") -> KeyFileFormat.PEM + in Regex("""^-+BEGIN .*OPENSSH PRIVATE KEY-+""") -> KeyFileFormat.PEM + in Regex("""^-+BEGIN .*EC PARAMETERS-+""") -> KeyFileFormat.PEM + in Regex("""^-+BEGIN .*ENCRYPTED PRIVATE KEY-+""") -> KeyFileFormat.ENCRYPTED_PEM + in Regex("""^-+BEGIN .*PRIVATE KEY-+""") -> KeyFileFormat.PEM + else -> throw InvalidFileFormat(input.path, "Unknown file format (expected ${KeyFileFormat.getNames()}).") + } + } + + private suspend fun getKey(input: File, keyType: KeyFileFormat = getKeyType(input)): LocalKey { + val inputContent = input.readText() + try { + return runCatching { + when (keyType) { + KeyFileFormat.JWK -> LocalKey.importJWK(inputContent).getOrThrow() + KeyFileFormat.PEM -> LocalKey.importPEM(inputContent).getOrThrow() + KeyFileFormat.ENCRYPTED_PEM -> LocalKey.importPEM(decrypt(input).getOrThrow()).getOrThrow() + } + }.getOrThrow() + } catch (e: Throwable) { + var mainMsg = "Invalid file format." + var complementaryMsg = "Use the --verbose flag to get more details." + + if (this.commonOptions.verbose) { + complementaryMsg = e.localizedMessage ?: "No more details to show." + } + throw InvalidFileFormat(input.absolutePath, "$mainMsg $complementaryMsg") + } + + } + + /** + * If the provided file is of type JWK, convert it to PEM. + * If PEM, convert it to JWK. + */ + private fun getOutputKeyType(inputKeyType: KeyFileFormat): KeyFileFormat = + if (inputKeyType == KeyFileFormat.JWK) KeyFileFormat.PEM else KeyFileFormat.JWK + + + /** + Convert provided source key to specified target key type + */ + private suspend fun convertKey(inputKey: Key, targetKeyType: KeyFileFormat): String { + try { + return when (targetKeyType) { + KeyFileFormat.JWK -> inputKey.exportJWK() + KeyFileFormat.PEM -> inputKey.exportPEM() + KeyFileFormat.ENCRYPTED_PEM -> inputKey.exportPEM() + } + } catch (e: Throwable) { + val mainMsg = "Oops. Something went wrong when converting the key." + var complementaryMsg = "Use the --verbose flag to get more details." + + if (this.commonOptions.verbose) { + complementaryMsg = e.localizedMessage ?: "No more details to show." + } + + throw UsageError("$mainMsg $complementaryMsg") + } + } + + private fun decrypt(input: File): Result { + + lateinit var k: PrivateKey + + try { + val decipherKey: String + + + if (passphrase == null) { + decipherKey = terminal.prompt("Key encrypted. Please, inform the passphrase to decipher it")!! + if (decipherKey == null) { // TODO: Can happen? + return Result.failure(BadParameterValue(passphrase!!)) + } + } else { + decipherKey = passphrase as String + } + val decryptedPEM = decryptKey(decipherKey) // as BCRSAPrivateCrtKey + return Result.success(decryptedPEM) + } catch (e: Exception) { + return Result.failure(e) + } + } + + // TODO: Extract + fun decryptKey(passphrase: String): String { + Security.addProvider(BouncyCastleProvider()) // TODO: do not add at every function call + + try { + val pemParser = PEMParser(FileReader(input)) + val pemObject = runCatching { + pemParser.readObject() ?: error("No object in PEM") + }.getOrElse { throw IllegalArgumentException("Could not parse object from PEM", it) } + val pki = when (pemObject) { + is PKCS8EncryptedPrivateKeyInfo -> { + val decryptorProviderBuilder = JcePKCSPBEInputDecryptorProviderBuilder().setProvider("BC") + val inputDecryptorProvider = decryptorProviderBuilder.build(passphrase.toCharArray()) + + pemObject.decryptPrivateKeyInfo(inputDecryptorProvider) + } + + is PEMEncryptedKeyPair -> { + val pkp = pemObject.decryptKeyPair(BcPEMDecryptorProvider(passphrase.toCharArray())) + + pkp.privateKeyInfo + } + + else -> throw PKCSException("Invalid encrypted private key class: " + pemObject::class.qualifiedName) + } + + val converter = JcaPEMKeyConverter().setProvider("BC") + + val encodedKey = Base64.getEncoder().encodeToString(converter.getPrivateKey(pki).encoded) + + return """ + -----BEGIN PRIVATE KEY----- + $encodedKey + -----END PRIVATE KEY----- + """.trimIndent() + } catch (e: Exception) { + throw Exception("Failed to load key from $input: $e") + } + } +} diff --git a/waltid-cli/src/commonMain/kotlin/id/walt/cli/commands/KeyGenerateCmd.kt b/waltid-cli/src/commonMain/kotlin/id/walt/cli/commands/KeyGenerateCmd.kt new file mode 100644 index 000000000..0dbc10a7e --- /dev/null +++ b/waltid-cli/src/commonMain/kotlin/id/walt/cli/commands/KeyGenerateCmd.kt @@ -0,0 +1,77 @@ +package id.walt.cli.commands + +import com.github.ajalt.clikt.core.CliktCommand +import com.github.ajalt.clikt.core.terminal +import com.github.ajalt.clikt.parameters.groups.provideDelegate +import com.github.ajalt.clikt.parameters.options.default +import com.github.ajalt.clikt.parameters.options.help +import com.github.ajalt.clikt.parameters.options.option +import com.github.ajalt.clikt.parameters.types.enum +import com.github.ajalt.clikt.parameters.types.path +import com.github.ajalt.mordant.markdown.Markdown +import com.github.ajalt.mordant.rendering.TextColors +import com.github.ajalt.mordant.rendering.TextStyles +import com.github.ajalt.mordant.terminal.YesNoPrompt +import id.walt.crypto.keys.KeyType +import id.walt.crypto.keys.LocalKey +import kotlinx.coroutines.runBlocking +import kotlin.io.path.Path +import kotlin.io.path.absolutePathString +import kotlin.io.path.exists +import kotlin.io.path.writeText +import kotlin.js.ExperimentalJsExport + +@OptIn(ExperimentalJsExport::class) +class KeyGenerateCmd : CliktCommand( + name = "generate", + help = "Generates a new cryptographic key.", + // printHelpOnEmptyArgs = true +) { + + private val acceptedKeyTypes = KeyType.entries.joinToString(" | ") + + private val keyType by option("-t", "--keyType") + .enum() + .help("Key type to use. Possible values are: [${acceptedKeyTypes}]. Default value is " + KeyType.Ed25519.name) + .default(KeyType.Ed25519) + + private val optOutputFilePath by option("-o", "--output") + .path() + .help("File path to save the generated key. Default value is .json") + + private val commonOptions by CommonOptions() + + override fun run() { + echo(TextStyles.dim("Generating key of type ${keyType.name}...")) + runBlocking { + val key = LocalKey.generate(keyType) + + echo(TextStyles.dim("Key thumbprint is: ${key.getThumbprint()}")) + + val jwk = key.exportJWKPretty() + + echo(TextColors.green("Generated Key (JWK):")) + terminal.println(Markdown(""" + |```json + |$jwk + |``` + """.trimMargin())) + + val outputFile = optOutputFilePath ?: Path("${key.getKeyId()}.json") + + if (outputFile.exists() + && YesNoPrompt( + "The file \"${outputFile.absolutePathString()}\" already exists, do you want to overwrite it?", + terminal + ).ask() == false + ) { + echo("Will not overwrite output file.") + return@runBlocking + } + + outputFile.writeText(jwk) + echo("${TextColors.brightGreen("Done.")} Key saved at file \"${outputFile.absolutePathString()}\".") + } + } +} + diff --git a/waltid-cli/src/jvmTest/kotlin/id/walt/cli/ImportAndExportTests.kt b/waltid-cli/src/jvmTest/kotlin/id/walt/cli/ImportAndExportTests.kt new file mode 100644 index 000000000..6ec8d9144 --- /dev/null +++ b/waltid-cli/src/jvmTest/kotlin/id/walt/cli/ImportAndExportTests.kt @@ -0,0 +1,63 @@ +package id.walt.cli + +import id.walt.crypto.keys.KeyType +import id.walt.crypto.keys.LocalKey +import kotlinx.coroutines.runBlocking +import kotlinx.serialization.json.Json +import kotlinx.serialization.json.jsonObject +import org.junit.jupiter.api.assertDoesNotThrow +import kotlin.test.Ignore +import kotlin.test.Test +import kotlin.test.assertEquals + +class ImportAndExportTests { + + @Test + @Ignore + fun testRSA() { + testBidirectionalConversion(KeyType.RSA) // Not working. Keving is looking at it. + } + + @Test + @Ignore + fun testEd25519() { + // kotlin.NotImplementedError: Ed25519 keys cannot be exported as PEM yet. + testBidirectionalConversion(KeyType.Ed25519) + } + + @Test + fun testSecp256k1() { + testBidirectionalConversion(KeyType.secp256k1) + } + + @Test + fun testSecp256r1() { + testBidirectionalConversion(KeyType.secp256r1) + } + + private fun testBidirectionalConversion(keyType: KeyType) { + + val generatedKey = runBlocking { LocalKey.generate(keyType) } + val generatedJwk = runBlocking { generatedKey.exportJWK() } + + val importedKeyFromJwk = assertDoesNotThrow { + runBlocking { LocalKey.importJWK(generatedJwk).getOrThrow() } + } + + println("Export PEM:") + val exportedPem = runBlocking { importedKeyFromJwk.exportPEM() } + println(exportedPem) + + val importedKeyFromPem = assertDoesNotThrow { + runBlocking { LocalKey.importPEM(exportedPem).getOrThrow() } + } + + val exportedJwk = runBlocking { importedKeyFromPem.exportJWK() } + + + assertEquals( + expected = Json.parseToJsonElement(generatedJwk).jsonObject.filterKeys { it != "kid" }, + actual = Json.parseToJsonElement(exportedJwk).jsonObject.filterKeys { it != "kid" }, + ) + } +} diff --git a/waltid-cli/src/jvmTest/kotlin/id/walt/cli/PemImportTests.kt b/waltid-cli/src/jvmTest/kotlin/id/walt/cli/PemImportTests.kt new file mode 100644 index 000000000..a94a9a808 --- /dev/null +++ b/waltid-cli/src/jvmTest/kotlin/id/walt/cli/PemImportTests.kt @@ -0,0 +1,40 @@ +package id.walt.cli + +import id.walt.crypto.keys.LocalKey +import kotlinx.coroutines.test.runTest +import kotlin.test.Test + +class PemImportTests { + + private val publicSecp256k1Key = """ + -----BEGIN PUBLIC KEY----- + MFYwEAYHKoZIzj0CAQYFK4EEAAoDQgAED7KA0wd4qOX37HvlneDt1XdfV8fVRG2x + WQGCjsO8s0fxZ09kNE4bMKQoDfpRSnplCBJ93SnPHUFSJQu5CeFMew== + -----END PUBLIC KEY----- + """.trimIndent() + + private val privateSecp256k1Key = """ + -----BEGIN PRIVATE KEY----- + MIGEAgEAMBAGByqGSM49AgEGBSuBBAAKBG0wawIBAQQghItf3kprlQm9bYmnDKch + RxBRCWaQBhKi+b2sSjCxCKmhRANCAAQPsoDTB3io5ffse+Wd4O3Vd19Xx9VEbbFZ + AYKOw7yzR/FnT2Q0ThswpCgN+lFKemUIEn3dKc8dQVIlC7kJ4Ux7 + -----END PRIVATE KEY----- + -----BEGIN PUBLIC KEY----- + MFYwEAYHKoZIzj0CAQYFK4EEAAoDQgAED7KA0wd4qOX37HvlneDt1XdfV8fVRG2x + WQGCjsO8s0fxZ09kNE4bMKQoDfpRSnplCBJ93SnPHUFSJQu5CeFMew== + -----END PUBLIC KEY----- + """.trimIndent() + + @Test + fun testImportPublicSecp256k1KeyPem() = runTest { + val imported = LocalKey.importPEM(publicSecp256k1Key) + println(imported.getOrThrow()) + } + + @Test + fun testImportPrivateSecp256k1KeyPem() = runTest { + val imported = LocalKey.importPEM(privateSecp256k1Key) + println(imported.getOrThrow()) + } + +} diff --git a/waltid-cli/src/jvmTest/kotlin/id/walt/cli/WaltIdCmdTest.kt b/waltid-cli/src/jvmTest/kotlin/id/walt/cli/WaltIdCmdTest.kt new file mode 100644 index 000000000..138afb3ab --- /dev/null +++ b/waltid-cli/src/jvmTest/kotlin/id/walt/cli/WaltIdCmdTest.kt @@ -0,0 +1,29 @@ +package id.walt.cli + +import com.github.ajalt.clikt.core.PrintHelpMessage +import com.github.ajalt.clikt.testing.test +import kotlinx.coroutines.test.runTest +import kotlin.test.Test +import kotlin.test.assertContains +import kotlin.test.assertFailsWith + +class WaltIdCmdTest { + + @Test + fun testMainNoArgs() = runTest { + val command = WaltIdCmd() + assertFailsWith { + command.parse(emptyList()) + } + val result = command.test() + assertContains(result.stdout, "The walt.id CLI is a command line tool") + } + + @Test + fun testMainHelp() = runTest { + val command = WaltIdCmd() + assertFailsWith(message = "Walt.id CLI usage") { + command.parse(listOf("--help")) + } + } +} \ No newline at end of file diff --git a/waltid-cli/src/jvmTest/kotlin/id/walt/cli/WaltIdKeyCmdTest.kt b/waltid-cli/src/jvmTest/kotlin/id/walt/cli/WaltIdKeyCmdTest.kt new file mode 100644 index 000000000..fa2c81b2c --- /dev/null +++ b/waltid-cli/src/jvmTest/kotlin/id/walt/cli/WaltIdKeyCmdTest.kt @@ -0,0 +1,35 @@ +package id.walt.cli + +import com.github.ajalt.clikt.core.PrintHelpMessage +import com.github.ajalt.clikt.testing.test +import id.walt.cli.commands.KeyCmd +import kotlinx.coroutines.test.runTest +import kotlin.test.Test +import kotlin.test.assertFailsWith +import kotlin.test.assertTrue + +class WaltIdKeyCmdTest { + + @Test + fun testKeyCmd() = runTest { + val command = KeyCmd() + val result = assertFailsWith { + command.parse(emptyList()) + } + + // TODO worth it? + result.message?.let { assertTrue(it.contains("Key management")) } + + val result2 = command.test() + assertTrue(result2.stdout.contains("Key management features", ignoreCase = true)) + } + + @Test + fun testKeyHelp() { + val command = KeyCmd() + + assertFailsWith { + command.parse(listOf("--help")) + } + } +} diff --git a/waltid-cli/src/jvmTest/kotlin/id/walt/cli/WaltIdKeyConvertCmdTest.kt b/waltid-cli/src/jvmTest/kotlin/id/walt/cli/WaltIdKeyConvertCmdTest.kt new file mode 100644 index 000000000..a702d7680 --- /dev/null +++ b/waltid-cli/src/jvmTest/kotlin/id/walt/cli/WaltIdKeyConvertCmdTest.kt @@ -0,0 +1,530 @@ +package id.walt.cli + +import com.github.ajalt.clikt.core.* +import com.github.ajalt.clikt.testing.test +import com.github.ajalt.mordant.rendering.AnsiLevel +import com.github.ajalt.mordant.terminal.PrintRequest +import com.github.ajalt.mordant.terminal.Terminal +import com.github.ajalt.mordant.terminal.TerminalInfo +import com.github.ajalt.mordant.terminal.TerminalInterface +import id.walt.cli.commands.KeyConvertCmd +import kotlinx.coroutines.channels.Channel +import kotlinx.coroutines.test.runTest +import java.io.File +import java.io.FileNotFoundException +import java.io.StringWriter +import java.net.URI +import kotlin.test.* + +class WaltIdKeyConvertCmdTest { + + fun checkHelpMessage(command: KeyConvertCmd, args: List) { + assertFailsWith { + command.parse(args) + } + + val result = command.test(args) + val reHelpMsg = "Convert key files".toRegex() + val reOpt1 = "-i, --input".toRegex() + val reOpt2 = "-o, --output".toRegex() + + assertContains(result.stdout, reHelpMsg) + assertContains(result.stdout, reOpt1) + assertContains(result.stdout, reOpt2) + } + + @Test + fun `should print a help message`() { + checkHelpMessage(KeyConvertCmd(), listOf("--help")) + } + + // class WaltIdKeyConvertCmdStdOutTest { + @Test + fun `should print help message when called with no parameter`() = runTest { + val command = KeyConvertCmd() + val result = command.test(emptyList()) + val expected = "Convert key files.*".toRegex() + assertContains(result.output, expected) + } + + @Test + fun `should fail if --input is provided with no value`() { + val command = KeyConvertCmd() + + val failure = assertFailsWith { + command.parse(listOf("-i")) + } + + assertTrue(failure.errors.any { it is IncorrectOptionValueCount }) + assertTrue(failure.errors.any { it is MissingOption }) + } + + @Test + fun `should fail if --ouput is provided with no value`() { + val command = KeyConvertCmd() + + val failure = assertFailsWith { + command.parse(listOf("-o")) + } + + assertTrue(failure.errors.any { it is IncorrectOptionValueCount }) + assertTrue(failure.errors.any { it is MissingOption }) + } + + @Test + fun `should NOT fail if --output is not provided`() { + + // openssl genrsa -out src/jvmTest/resources/rsa_by_openssl_pvt_key.pem 3072 + // openssl rsa -in src/jvmTest/resources/rsa_by_openssl_pvt_key.pem -pubout -out src/jvmTest/resources/rsa_by_openssl_pub_key.pem + val inputFileName = "rsa_by_openssl_pvt_key.pem" + val outputFileName = "rsa_by_openssl_pvt_key.jwk" + + val inputFilePath = getFilePath(inputFileName) + val outputFilePath = getOutputFilePath(inputFilePath, outputFileName) + + KeyConvertCmd().parse(listOf("--input=${inputFilePath}")) + + // If the execution reaches this point, it means the command above didn't throw any exception + assertTrue(true) + + deleteOutputFile(outputFilePath) + } + + @Test + fun `should fail if a non-existent input file is provided`() { + val command = KeyConvertCmd() + + val inputFilename = "foo.jwk" + val result = command.test(listOf("-i$inputFilename")) + + val expected = """.*file "$inputFilename" does not exist.*""".toRegex(RegexOption.IGNORE_CASE) + assertContains(result.stderr, expected) + } + + @Test + fun `should fail with invalid input file`() { + + // Stored in src/jvmTest/resources + val inputFileName = "invalidKey.jwk" + var inputFilePath = getFilePath(inputFileName) + + var result = KeyConvertCmd().test("--input=\"$inputFilePath\" --verbose") + var expectedErrorMessage = ".*Invalid file format*".toRegex() + assertContains(result.stderr, expectedErrorMessage) + + result = KeyConvertCmd().test("--input=\"$inputFilePath\" --verbose") + expectedErrorMessage = ".*Missing key type \"kty\" parameter*".toRegex() + assertContains(result.stderr, expectedErrorMessage) + } + + @Test + fun `should prompt for overwrite confirmation when the output file already exists`() { + // Stored in src/jvmTest/resources + val inputFileName = "rsa_by_openssl_pub_key.pem" + val outputFileName = "existingFile.jwk" + + val inputFilePath = getFilePath(inputFileName) + val outputFilePath = getOutputFilePath(inputFilePath, outputFileName) + + // Creates the output file to simulate its previous existence + File(outputFilePath).createNewFile() + + val result = KeyConvertCmd().test("--input=\"$inputFilePath\" --output=\"$outputFilePath\"") + val expectedOutput = """.*The file "$outputFilePath" already exists.*""".toRegex() + + // Assert successful logging message + assertContains(result.stdout, expectedOutput) + } + + @Test + fun `should convert JWT input file to PEM`() { + + // Stored in src/jvmTest/resources + val inputFileName = "ed25519_by_waltid_pvt_key.jwk" + val outputFileName = "ed25519_by_waltid_pvt_key.pem" + + val inputFilePath = getFilePath(inputFileName) + val outputFilePath = getOutputFilePath(inputFilePath, outputFileName) + + // Only as long as Ed25519 is not fully supported in LocalKey.exportPEM() + val result1 = KeyConvertCmd().test("--input=\"$inputFilePath\"") + assertContains(result1.stderr, "Something went wrong when converting the key") + + val result2 = KeyConvertCmd().test("--input=\"$inputFilePath\" --verbose") + assertContains(result2.stderr, "Ed25519 keys cannot be exported as PEM yet") + + deleteOutputFile(outputFilePath) + } + + @Test + fun `should convert a PEM file to JWK`() { + + // Stored in src/jvmTest/resources + val inputFileName = "rsa_by_openssl_pub_key.pem" + val outputFileName = "rsa_by_openssl_pub_key.jwk" + + val inputFilePath = getFilePath(inputFileName) + val outputFilePath = getOutputFilePath(inputFilePath, outputFileName) + + deleteOutputFile(outputFilePath) + + val result = KeyConvertCmd().test("--input=\"$inputFilePath\"") + val expectedOutput = """.*Done. Converted "$inputFilePath" to "$outputFilePath".*""".toRegex() + + // Assert successful logging message + assertContains(result.stdout, expectedOutput) + + deleteOutputFile(outputFilePath) + } + + @Test + fun `should convert RSA public key PEM file to a valid JWK`() { + + // Stored in src/jvmTest/resources + val inputFileName = "rsa_by_openssl_pub_key.pem" + val outputFileName = "rsa_by_openssl_pub_key.jwk" + + // Assert output file content + val expectedJWKFragments = listOf( + """"kty":"RSA"""", + """("kid":".*")*""", // Key id + """"n":".*"""", // Modulus parameter + """"e":"AQAB"""", // Exponent parameter + // "\"p\":\".*\"", // First prime factor + // "\"q\":\".*\"", // Second prime factor + // "\"d\":\".*\"", // Private expoent + // "\"qi\":\".*\"", // First CRT coefficient + // "\"dp\":\".*\"", // First factor CRT expoent + // "\"dq\":\".*\"", // Secobnd factor CRT expoent + ) + + testSuccessfulConvertion(inputFileName, outputFileName, expectedJWKFragments) + } + + @Test + fun `should convert RSA private key PEM file to a valid JWK`() { + + // Stored in src/jvmTest/resources + val inputFileName = "rsa_by_openssl_pvt_key.pem" + val outputFileName = "rsa_by_openssl_pvt_key.jwk" + + // Assert output file content + val expectedJWKFragments = listOf( + """"kty":"RSA"""", // Key type + """("kid":".*")*""", // Key id + """"n":".*"""", // Modulus parameter + """"e":".*"""", // Exponent parameter + """"p":".*"""", // First prime factor + """"q":".*"""", // Second prime factor + """"d":".*"""", // Private expoent + """"qi":".*"""", // First CRT coefficient + """"dp":".*"""", // First factor CRT exponent + """"dq":".*"""", // Second factor CRT exponent + ) + + testSuccessfulConvertion(inputFileName, outputFileName, expectedJWKFragments) + } + + @Test + @Ignore + fun `should ask for the passphrase if input PEM file is encrypted and no passphrase is provided`() = runTest { + val inputFileName = "rsa_encrypted_private_key.pem" + val outputFileName = "rsa_encrypted_private_key.jwk" + + val inputFilePath = getFilePath(inputFileName) + + class PassphraseTerminal : TerminalInterface { + override val info: TerminalInfo + get() = TerminalInfo( + width = 0, + height = 0, + ansiLevel = AnsiLevel.NONE, + ansiHyperLinks = false, + outputInteractive = false, + inputInteractive = false, + crClearsLine = false + ) + + override fun completePrintRequest(request: PrintRequest) { + StringWriter().write(request.text) + } + + override fun readLineOrNull(hideInput: Boolean): String { + return "123123" + } + + suspend fun answerPrompt(input: String) = Channel().send(input) + + } + + val result = KeyConvertCmd().context { terminal = Terminal(terminalInterface = PassphraseTerminal()) } + .test("--input=\"${inputFilePath}\"") + + println(result) + + // + // KeyConvertCmd().testkit("--input", inputFilePath) { + // expectOutput() // Reading key ... + // assertContains(expectOutput(), ".*Key encrypted. Please, inform the passphrase to decipher it.*".toRegex()) + // provideInput("123123") + // // expectOutput() // Converting key + // // assertContains(expectOutput(), ".*Converted Key .JWK.*".toRegex()) + // // ignoreOutputs() + // } + } + + @Test + fun `should NOT ask for the passphrase if input PEM file is encrypted and --passphrase is provided`() { + + // openssl genrsa -aes256 -passout pass:123123 -out rsa_by_openssl_encrypted_pvt_key.pem 2048 + val inputFileName = "rsa_by_openssl_encrypted_pvt_key.pem" + val outputFileName = "rsa_by_openssl_encrypted_pvt_key.jwk" + + val inputFilePath = getFilePath(inputFileName) + val outputFilePath = getOutputFilePath(inputFilePath, outputFileName) + + val result = KeyConvertCmd().test("--input=\"$inputFilePath\" --passphrase=123123") + + val expectedOutput = """.*Done. Converted "$inputFilePath" to "$outputFilePath".*""".toRegex() + + // Assert successful logging message + assertContains(result.stdout, expectedOutput) + + deleteOutputFile(outputFilePath) + } + + @Test + fun `should convert RSA PEM public key extracted from an encrypted private key`() { + + // openssl rsa -in rsa_by_openssl_encrypted_pvt_key.pem -passin pass:123123 -pubout -out rsa_by_openssl_encrypted_pub_key.pem + val inputFileName = "rsa_by_openssl_encrypted_pub_key.pem" + val outputFileName = "rsa_by_openssl_encrypted_pub_key.jwk" + + val inputFilePath = getFilePath(inputFileName) + val outputFilePath = getOutputFilePath(inputFilePath, outputFileName) + + val result = KeyConvertCmd().test("--input=\"$inputFilePath\"") + + val expectedOutput = """.*Done. Converted "$inputFilePath" to "$outputFilePath".*""".toRegex() + + // Assert successful logging message + assertContains(result.stdout, expectedOutput) + + deleteOutputFile(outputFilePath) + + } + + @Test + fun `should convert encrypted RSA private key PEM file to a valid JWK`() { + + // Stored in src/jvmTest/resources + val inputFileName = "rsa_by_openssl_encrypted_pvt_key.pem" + val outputFileName = "rsa_by_openssl_encrypted_pvt_key.jwk" + + // Assert output file content + val expectedJWKFragments = listOf( + """"kty":"RSA"""", // Key type + """("kid":".*")*""", // Key id + """"n":".*"""", // Modulus parameter + """"e":".*"""", // Exponent parameter + """"p":".*"""", // First prime factor + """"q":".*"""", // Second prime factor + """"d":".*"""", // Private expoent + """"qi":".*"""", // First CRT coefficient + """"dp":".*"""", // First factor CRT exponent + """"dq":".*"""", // Second factor CRT exponent + ) + + val extraArgs = "--passphrase=123123" + + testSuccessfulConvertion(inputFileName, outputFileName, expectedJWKFragments, extraArgs) + } + + @Test + // @ValueSource(strings = {"ed25519_pub_key.pem", "ed25519_pvt_key.pem"}) --> JUnit dependency :-( + fun `should fail when trying to convert Ed25519 PEM file`() { + + // ssh-keygen -t ed25519 -f ed25519_pvt_key_by_openssh.pem + testFailingConversion("ed25519_by_openssh_pvt_key.pem", "Invalid file format", false) + testFailingConversion("ed25519_by_openssh_pvt_key.pem", "unrecognised object: OPENSSH PRIVATE KEY", true) + + // openssl genpkey -algorithm ed25519 -out ed25519_pvt_key_by_openssl.pem + testFailingConversion("ed25519_by_openssl_pvt_key.pem", "Invalid file format", false) + testFailingConversion("ed25519_by_openssl_pvt_key.pem", "Missing PEM-encoded public key to construct JWK", true) + + // openssl pkey -pubout -in src/jvmTest/resources/ed25519_pvt_key_by_openssl.pem -out src/jvmTest/resources/ed25519_pub_key_by_openssl.pem + testFailingConversion("ed25519_by_openssl_pub_key.pem", "Invalid file format", false) + testFailingConversion("ed25519_by_openssl_pub_key.pem", "Unsupported algorithm of PEM-encoded key: EdDSA", true) + } + + @Test + @Ignore + fun `should convert Ed25519 OpenSSL PEM file`() = Unit + + @Test + @Ignore + fun `should convert Ed25519 OpenSSH PEM file`() = Unit + + @Test + fun `should convert secp256k1 PEM file with public and private key inside to JWK`() { + + // openssl ecparam -genkey -name secp256k1 -out src/jvmTest/resources/secp256k1_by_openssl_pub_pvt_key.pem + val inputFileName = "secp256k1_by_openssl_pub_pvt_key.pem" + val outputFileName = "secp256k1_by_openssl_pub_pvt_key.jwk" + + // Assert output file content + val expectedJWKFragments = listOf( + """"kty":"EC"""", + """"crv":"secp256k1"""", + """"x":".*"""", + """"y":".*"""" + ) + + testSuccessfulConvertion(inputFileName, outputFileName, expectedJWKFragments, "") + } + + @Test + @Ignore + fun `should convert secp256k1 PEM file only with public key inside`() { + + // Doesn't work yet because BouncyCastle doesn't supoprt PEM object "BEGIN EC PUBLIC KEY" + + // openssl pkey -in secp256k1_by_openssl_pvt_key.pem -pubout -out secp256k1_by_openssl_pub_key.pem + val inputFileName = "secp256k1_by_openssl_pub_key.pem" + val outputFileName = "secp256k1_by_openssl_pub_key.jwk" + + // Assert output file content + val expectedJWKFragments = listOf( + """"kty":"EC"""", + """"crv":"secp256k1"""", + """"x":".*"""", + """"y":".*"""" + ) + + testSuccessfulConvertion(inputFileName, outputFileName, expectedJWKFragments, "") + } + + @Test + fun `should fail when trying to convert secp256k1 PEM file only with private key inside`() { + + // ./waltid-cli.sh key generate -tsecp256k1 --output=src/jvmTest/resources/secp256k1_by_waltid_pvt_key.jwk + // ./waltid-cli.sh key convert --input=src/jvmTest/resources/secp256k1_by_waltid_pvt_key.jwk + testFailingConversion( + "secp256k1_by_waltid_pvt_key.pem", + "incorrect format in file", + false, + ) + + testFailingConversion( + "secp256k1_by_waltid_pvt_key.pem", + """the return value of "org.bouncycastle.openssl.PEMKeyPair.getPublicKeyInfo()" is null""", + true, + ) + + // openssl storeutl -keys src/jvmTest/resources/secp256k1_by_openssl_pub_pvt_key.pem > src/jvmTest/resources/secp256k1_by_openssl_pvt_key.pem + testFailingConversion( + "secp256k1_by_openssl_pvt_key.pem", + "Invalid file format", + false, + ) + + testFailingConversion( + "secp256k1_by_openssl_pvt_key.pem", + "Missing PEM-encoded public key to construct JWK", + true, + ) + } + + @Test + @Ignore + fun `should convert Ed25519 public key PEM file to a valid JWK`() = Unit + + @Test + @Ignore + fun `should convert Ed25519 private key PEM file to a valid JWK`() = Unit + + @Test + @Ignore + fun `should convert Ed25519 pub & pvt key PEM file to a valid JWK`() = Unit + + @Test + @Ignore + fun `should convert secp256k1 public key PEM file to a valid JWK`() = Unit + + @Test + @Ignore + fun `should convert secp256k1 private key PEM file to a valid JWK`() = Unit + + @Test + @Ignore + fun `should convert secp256k1 pub & pvt key PEM file to a valid JWK`() = Unit + + @Test + @Ignore + fun `should convert secp256r1 public key PEM file to a valid JWK`() = Unit + + @Test + @Ignore + fun `should convert secp256r1 private key PEM file to a valid JWK`() = Unit + + @Test + @Ignore + fun `should convert secp256r1 pub & pvt key PEM file to a valid JWK`() = Unit + + fun getFilePath(filename: String): String { + // The returned URL has white spaces replaced by %20. + // So, we need to decode it first to get rid of %20 from the file path + this.javaClass.getClassLoader().getResource(filename)?.let { + return URI(it.toString()).path + } + + throw FileNotFoundException(filename) + } + + fun getOutputFilePath(inputFilePath: String, outputFileName: String): String { + return "${inputFilePath.dropLastWhile { it != '/' }}$outputFileName" + } + + fun deleteOutputFile(outputFilePath: String) { + // TODO: Need to check if exists? + File(outputFilePath).delete() + } + + private fun testSuccessfulConvertion( + inputFileName: String, outputFileName: String, expectedFragments: List, extraArgs: String = "" + ) { + val inputFilePath = getFilePath(inputFileName) + val outputFilePath = getOutputFilePath(inputFilePath, outputFileName) + + deleteOutputFile(outputFilePath) + val result = KeyConvertCmd().test("--input=\"$inputFilePath\" $extraArgs") + + val expectedOutput = """.*Done. Converted "$inputFilePath" to "$outputFilePath".*""".toRegex() + + // Assert successful logging message + assertContains(result.stdout, expectedOutput) + + val convertedContent = File(outputFilePath).readText() + + // Assert the converted file structure + expectedFragments.forEach { + assertContains(convertedContent, it.toRegex()) + } + + deleteOutputFile(outputFilePath) + } + + fun testFailingConversion(inputFileName: String, expectedErrorMessage: String, verbose: Boolean = false) { + + val inputFilePath = getFilePath(inputFileName) + var args = "--input=\"$inputFilePath\"" + + if (verbose) { + args = "$args --verbose" + } + + val result = KeyConvertCmd().test(args) + assertContains(result.stderr, expectedErrorMessage) + } + +} diff --git a/waltid-cli/src/jvmTest/kotlin/id/walt/cli/WaltIdKeyGenerateCmdTest.kt b/waltid-cli/src/jvmTest/kotlin/id/walt/cli/WaltIdKeyGenerateCmdTest.kt new file mode 100644 index 000000000..3923ca7a2 --- /dev/null +++ b/waltid-cli/src/jvmTest/kotlin/id/walt/cli/WaltIdKeyGenerateCmdTest.kt @@ -0,0 +1,215 @@ +package id.walt.cli + +import com.github.ajalt.clikt.core.BadParameterValue +import com.github.ajalt.clikt.core.IncorrectOptionValueCount +import com.github.ajalt.clikt.core.PrintHelpMessage +import com.github.ajalt.clikt.testing.CliktCommandTestResult +import com.github.ajalt.clikt.testing.test +import id.walt.cli.commands.KeyGenerateCmd +import id.walt.crypto.keys.KeyType +import kotlinx.coroutines.test.runTest +import java.io.File +import kotlin.test.Test +import kotlin.test.assertContains +import kotlin.test.assertFailsWith +import kotlin.test.assertTrue + +class WaltIdKeyGenerateCmdTest { + + private fun checkHelpMessage(command: KeyGenerateCmd, args: List) { + assertFailsWith { + command.parse(args) + } + + val result = command.test(args) + val reHelpMsg = "Generates a new cryptographic key".toRegex() + val reOpt1 = "-t, --keyType".toRegex() + + assertContains(result.stdout, reHelpMsg) + assertContains(result.stdout, reOpt1) + } + + fun deleteGeneratedFile(output: String) { + val generatedFilePath = Regex(".*Key saved at file \"(.*?)\"").findAll(output).toList()[0].groupValues[1] + File(generatedFilePath).delete() + } + + @Test + fun `should print a help message`() { + checkHelpMessage(KeyGenerateCmd(), listOf("--help")) + } + + @Test + fun `should generate an Ed25519 key when no --keyType is provided`() = runTest { + val command = KeyGenerateCmd() + val result = command.test(emptyList()) + val expected = ".*Generating key of type ${KeyType.Ed25519.name}.*".toRegex() + assertContains(result.output, expected) + + deleteGeneratedFile(result.output) + } + + @Test + fun `should fail if --keyType is provided with no value`() { + val command = KeyGenerateCmd() + + assertFailsWith { + command.parse(listOf("-t")) + } + + assertFailsWith { + command.parse(listOf("--keyType")) + } + } + + @Test + fun `should fail if an invalid --keyType is provided`() { + val command = KeyGenerateCmd() + + // val result = command.test(listOf("-t foo")) + // --- result.output + // Usage: generate [] + // + // Error: invalid value for -t: invalid choice: foo. (choose from ED25519, SECP256K1, SECP256R1, RSA) + + val failure = assertFailsWith { + command.parse(listOf("-t foo")) + } + + val expected = "invalid choice".toRegex() + failure.message?.let { assertContains(it, expected) } + } + + @Test + fun `should generate key of type Ed25519`() = runTest { + val command = KeyGenerateCmd() + + val result = command.test("--keyType=Ed25519") + val expected1 = ".*Generated Key.*" + val expected2 = listOf( + """"kty": "OKP"""", + """"d": ".*?"""", + """"crv": "Ed25519"""", + """"kid": ".*?"""", + """"x": ".*?"""" + ) + + assertExpectations(result, expected1, expected2) + + deleteGeneratedFile(result.output) + } + + @Test + fun `should generate key of type secp256k1`() = runTest { + val command = KeyGenerateCmd() + + val result = command.test("--keyType=secp256k1") + val expected1 = ".*Done. Key saved at file.*" + val expected2 = listOf( + """"kty": "EC"""", + """"d": ".*?"""", + """"crv": "secp256k1"""", + """"kid": ".*?"""", + """"x": ".*?"""" + ) + + assertExpectations(result, expected1, expected2) + + deleteGeneratedFile(result.output) + } + + @Test + fun `should ignore key type case`() = runTest { + val command = KeyGenerateCmd() + + val expected1 = ".*Done. Key saved at file.*" + val expected2 = listOf( + """"kty": "EC"""", + """"d": ".*?"""", + """"crv": "secp256k1"""", + """"kid": ".*?"""", + """"x": ".*?"""", + """"y": ".*?"""" + ) + + + val result1 = command.test("--keyType=secp256k1") + assertExpectations(result1, expected1, expected2) + + val result2 = command.test("--keyType=SECP256k1") + assertExpectations(result2, expected1, expected2) + + val result3 = command.test("--keyType=SECP256K1") + assertExpectations(result3, expected1, expected2) + + deleteGeneratedFile(result1.output) + deleteGeneratedFile(result2.output) + deleteGeneratedFile(result3.output) + } + + @Test + fun `should save generated key in file`() { + val result = KeyGenerateCmd().test("--keyType=Ed25519") + val expectedAtStdOut = ".*Done. Key saved at file \"(.*)\"".toRegex() + assertContains(result.stdout, expectedAtStdOut) + + val filePath = expectedAtStdOut.find(result.stdout)!!.groupValues[1] + assertTrue(File(filePath).exists()) + + deleteGeneratedFile(result.output) + } + + @Test + fun `should generate file with a valid JWK`() { + val result = KeyGenerateCmd().test("--keyType=Ed25519") + + val expectedAtStdOut = ".*Done. Key saved at file \"(.*)\"".toRegex() + assertContains(result.stdout, expectedAtStdOut) + + val filePath = expectedAtStdOut.find(result.stdout)!!.groupValues[1] + val fileContent = File(filePath).readText() + + val validJWKFragments = listOf( + """"kty": "OKP"""", + """"d": ".*?"""", + """"crv": "Ed25519"""", + """"kid": ".*?"""", + """"x": ".*?"""" + ) + + validJWKFragments.forEach { + assertContains(fileContent, it.toRegex()) + } + + deleteGeneratedFile(result.output) + } + + @Test + fun `should override default file name`() { + val outputFileName = "myKey.json" + + File(outputFileName).delete() + + val result = KeyGenerateCmd().test("--keyType=Ed25519 --output=${outputFileName}") + val expectedAtStdOut = ".*Done. Key saved at file \"(.*)\"".toRegex() + + assertContains(result.stdout, expectedAtStdOut) + + val filePath = expectedAtStdOut.find(result.stdout)!!.groupValues[1] + assertTrue(File(filePath).exists()) + + deleteGeneratedFile(result.output) + } + + private fun assertExpectations( + result: CliktCommandTestResult, + expectedMessage: String, + expectedFileFormat: List + ) { + assertContains(result.stdout, expectedMessage.toRegex()) + + expectedFileFormat.forEach { + assertContains(result.stdout, it.toRegex()) + } + } +} \ No newline at end of file diff --git a/waltid-cli/src/jvmTest/resources/ed25519_by_openssh_pvt_key.pem b/waltid-cli/src/jvmTest/resources/ed25519_by_openssh_pvt_key.pem new file mode 100644 index 000000000..eb1730b4e --- /dev/null +++ b/waltid-cli/src/jvmTest/resources/ed25519_by_openssh_pvt_key.pem @@ -0,0 +1,8 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW +QyNTUxOQAAACBhpyuc1QRr08fLYZfRR0p1mUf3aRrwagJ1g+r0X1CBbgAAAKi7DEYIuwxG +CAAAAAtzc2gtZWQyNTUxOQAAACBhpyuc1QRr08fLYZfRR0p1mUf3aRrwagJ1g+r0X1CBbg +AAAEBYxAb/cQS+CLRFSE8H0BJ252f/SG2EQapO+QcyeU4gKGGnK5zVBGvTx8thl9FHSnWZ +R/dpGvBqAnWD6vRfUIFuAAAAH2FsZWdvbWVzQE1CUDE2QUxFUGVyc29uYWwubG9jYWwBAg +MEBQY= +-----END OPENSSH PRIVATE KEY----- diff --git a/waltid-cli/src/jvmTest/resources/ed25519_by_openssl_pub_key.pem b/waltid-cli/src/jvmTest/resources/ed25519_by_openssl_pub_key.pem new file mode 100644 index 000000000..111aea8e9 --- /dev/null +++ b/waltid-cli/src/jvmTest/resources/ed25519_by_openssl_pub_key.pem @@ -0,0 +1,3 @@ +-----BEGIN PUBLIC KEY----- +MCowBQYDK2VwAyEACm26Tn3wQ2qj78tXfZmVjXOWO/V0JYwnqMWJIrwlAd0= +-----END PUBLIC KEY----- diff --git a/waltid-cli/src/jvmTest/resources/ed25519_by_openssl_pvt_key.pem b/waltid-cli/src/jvmTest/resources/ed25519_by_openssl_pvt_key.pem new file mode 100644 index 000000000..6799a84b0 --- /dev/null +++ b/waltid-cli/src/jvmTest/resources/ed25519_by_openssl_pvt_key.pem @@ -0,0 +1,3 @@ +-----BEGIN PRIVATE KEY----- +MC4CAQAwBQYDK2VwBCIEIMoZ3BrOH9yneQl9cQdyNWl2s2Zm24W9W4+xXnDVWs73 +-----END PRIVATE KEY----- diff --git a/waltid-cli/src/jvmTest/resources/ed25519_by_waltid_pvt_key.jwk b/waltid-cli/src/jvmTest/resources/ed25519_by_waltid_pvt_key.jwk new file mode 100644 index 000000000..ff4351dee --- /dev/null +++ b/waltid-cli/src/jvmTest/resources/ed25519_by_waltid_pvt_key.jwk @@ -0,0 +1,7 @@ +{ + "kty": "OKP", + "d": "Uau3y-0bThfD5e2_sGPhFkosg97wQqW-bqyNhCadU5o", + "crv": "Ed25519", + "kid": "xBlnePRZ_vbsBJTCFdWE3FKzHJimxnw_7uwhUpqvNW8", + "x": "j277kMAZAA06U0E8dRmz3ypN2xFEQRxlxHbtxbcXziw" +} \ No newline at end of file diff --git a/waltid-cli/src/jvmTest/resources/invalidKey.jwk b/waltid-cli/src/jvmTest/resources/invalidKey.jwk new file mode 100644 index 000000000..55cfba5f9 --- /dev/null +++ b/waltid-cli/src/jvmTest/resources/invalidKey.jwk @@ -0,0 +1 @@ +{"xxkty":"OKP","xd":"yDANQWc2b22uuL9gBqgwVU96XJr9-l948fLLQzAJO0o","crv":"Ed25519","kid":"rtnKhFx38n9lUgTNJbaUBMBcjLN_PhHkx0gu6oMusJk","x":"Xa1CvkhiAVvTSlKgUeiU_nw6k2TdRsfm7uuO0JEBdpw"} \ No newline at end of file diff --git a/waltid-cli/src/jvmTest/resources/rsa_by_openssl_encrypted_pub_key.pem b/waltid-cli/src/jvmTest/resources/rsa_by_openssl_encrypted_pub_key.pem new file mode 100644 index 000000000..4ff0ecb9f --- /dev/null +++ b/waltid-cli/src/jvmTest/resources/rsa_by_openssl_encrypted_pub_key.pem @@ -0,0 +1,9 @@ +-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArhO4+TKkvEIzLw6vx7Tl +Knb2fmGvmvlDM1Nw5c3bYuhwbHJQMtqz0vjlLVIYg0nL/NoR1nQWvcBquZnrhXQ7 +HymwZcW5dbeORqqCMIW20GEZN3+3senJGDkl4wMMe/qBJMn6RutNKTTbycmvtXwp +NGSK90TRbRihPE2ljCGs32YalaPfKelYvHJ91CuG9lceMt7B3KUQ5L6SGPY5kuJj +WP47pP1cpafvdOEQd/VAai9s4zMvt91/cTZJoyJM/KeAvkRkyq8yBPQoa+2DMXmN +0iUFkiwLEsFYF330CUplXvZJ/LJXgRzCfFmYyc4Y3BlAbGd9JnFk4SH8pVBK43+s +nQIDAQAB +-----END PUBLIC KEY----- diff --git a/waltid-cli/src/jvmTest/resources/rsa_by_openssl_encrypted_pvt_key.pem b/waltid-cli/src/jvmTest/resources/rsa_by_openssl_encrypted_pvt_key.pem new file mode 100644 index 000000000..eeb1c880a --- /dev/null +++ b/waltid-cli/src/jvmTest/resources/rsa_by_openssl_encrypted_pvt_key.pem @@ -0,0 +1,30 @@ +-----BEGIN ENCRYPTED PRIVATE KEY----- +MIIFNTBfBgkqhkiG9w0BBQ0wUjAxBgkqhkiG9w0BBQwwJAQQ72u42p1klnLY7ktW +4MQbLAICCAAwDAYIKoZIhvcNAgkFADAdBglghkgBZQMEASoEEBsOy35OhQFY5ha2 +p9q19ekEggTQGKDe7phrUXKZii/66GrLuz1Gzu5ho2EO6Ow0SGXA+n3QyTWhSHI+ +nrHbbh4/qLEbCfvIrjEgmT86DPGtEDs4UUMK6DpBoIMtyg/bRlgzIrR7Nj2Q2R9c +G6QWPNR6y/Up6P1lmggqfMmRcVTI1/WEpTBppbXJdk71lEPQZpEK7C3cl4Q/Fo7C +e9FWdNHeks36BKen595QkbAlkyAvcE+uJVugld1sov80HPiQFjCxhBuQddGbBLq+ +VsTAh78+geYB12NqumsNvNK+ilfojqe8TyZJJqZ2qOiZXd+eHDoR34MNG3tQHkdB +SNo6X/nUV/N1VnEkXDMLGV0t4FNStxpZEz5vgXvCefX4hvWkgj9OAxSkYeqDBiNx +L/oVh4V1C8TH/uEnGnhFd2VsY1UfT+quL5eM6Ryz4tLJd4jW5eaytLXjiJuyMfkE +KnYBtzgPLGSEqIEAxnMq6ZsFb1DT68rkmF2jU2/BHArq+4uPe16uJf+x6u0jKzqF +/qmnBNKA3bwDq6Z7CbU/qOvR5QP0JPkLnrpACgtMqyd3452FmACZpst9KX1dZGHg +Qv9u5ILFooti98ZzHYLP2Yf4GhcQYF5ztLVSd0BTaqOZRCsfP+oUqRps6tR2K7IX +7dQYZzQorjZNdCDGXavq96QHq2xNytoqyIw3O8MrGsPvQmIYSYCn7ir7qNiVSPE6 +kUmA96sczHhS0c/VF+yRj7uFpwlf9uQunuW2VIvmyQsmiEH6uR3HsXQgqDG4zBjA +DRkwTM0UZv+Wh2fbABcN62KEFACKkYayssYibo6/T5oZYqZcgdjYkPrBKI1xBVou +AYWQRb2RjYexQQnZhlNHGJ/C9oknjEEb7nXRgQdUvvu9pVAubvGcIR/VfF5nUfS5 +ehlV5haCvPf7F1ncvB9rjLNLOQS+m3plAIVYratT1BDVMyRQiWuLC+paSADBdgLG +AIYD7LZj6lV5CmIimr7TvyrEDfbd9KpAhgOwJ0faPHUcQwisCg86Mp9Q3pTiP+IM +fhSwpBMM1lmoeiGRWHzj/ndO+ct0N1dsBinFGbqZVA6iee6TRMXdQVwWE7OZdRSk +NojFcmLyet//dABYD4CXT9pZoydmWwbKB0ou9NvylvG/FxyMeAaMjchRwzwv2Wbi +DpCTRRq8AdzblEhRhpbdPeSXCPt5StjSH6hezuWPbAGdyZC8IsLiXtW4OEO8f4W6 +SrD7CGeehp5q6XACe3hdsS9hnTeQLiIUBK9jrpfIzmvfiswnwCN2Yqsjwkazmf9t +OHvccg24oGmFUkh1yGW+CVppMgSqZD/q1//3p+SuylTop5Bx4UMhKpF3/K2GBzak +NqxRzMJFIOFPAS+7pKl3AMvSSdYvi3bXjq78822o4g3TelpbCh1qkCvACl3gMDfV +Jfv7XYpfpKjsgOidSVKebHES3M9CB/JUoaOmbmt82lpiFeIJflo1VjYZYK77D2Dn +Fbvh4dKlrjejOTXoVwQeChxkuBLjop8CWvFg42aMh8dzwU0OkDIgmhqtEp+3SPYt +TQfjHwb6RXJnPk9jg/hfjxfFrel4xwbYddcQJ5QETZVHEZlnOA90Itm/WIXXZRzW +/aeKlo1OmCz5AAo3/XgY88QlpVWMz/rqCc1r2GdvEHN8tmYOwna7ibk= +-----END ENCRYPTED PRIVATE KEY----- diff --git a/waltid-cli/src/jvmTest/resources/rsa_by_openssl_pub_key.jwk b/waltid-cli/src/jvmTest/resources/rsa_by_openssl_pub_key.jwk new file mode 100644 index 000000000..acbbd26e2 --- /dev/null +++ b/waltid-cli/src/jvmTest/resources/rsa_by_openssl_pub_key.jwk @@ -0,0 +1 @@ +{"kty":"RSA","e":"AQAB","n":"mxq0EXs-Y5i_by9CgzjO2HjRe5PTse_hMxf3dKyp4zPE87UnxpoCCanAnYvZj2KvNdl-IPpbIzFg8jsd5hopiO_BjXkTIVcp4JClB-gs94TCJxQaRBFEUn49Iffa7V1j5Vm2E6t6biG2tcgscDzvb_x32HKCGKC05MiXBTiBJ7s42Jrz9qCrRKJqf0QBp0-fHmhk5FsBZ1YfStyQY8xlxLyGolOU0REKnkaXGi2S0yHmUWMGzkPeByyLzDshcqgjw1Yv_ImftJoYe7zyp7cJeHkMRmu1ysCrP1TZv2s1tIbtYBGujFAVj1DDCuAO1ve-ISZwDBwJYuEGNKfH7kwqRThASn9Cy9o_Kuo7f1DrZKunJMpSOtfor1zumxezQoYyzVZ8jC22q5FJcoUH7zEIDQ_JmLOEw3uKyi9Qj3BgQDwkWm0oIJS1zg-q714PjTprGguHV1DThbzC46QomflZRowRACTTWcR3Lq2DguCZRfTZKWhitp4D8qlwTbEHgGuP"} \ No newline at end of file diff --git a/waltid-cli/src/jvmTest/resources/rsa_by_openssl_pub_key.pem b/waltid-cli/src/jvmTest/resources/rsa_by_openssl_pub_key.pem new file mode 100644 index 000000000..73aea5fc2 --- /dev/null +++ b/waltid-cli/src/jvmTest/resources/rsa_by_openssl_pub_key.pem @@ -0,0 +1,11 @@ +-----BEGIN PUBLIC KEY----- +MIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEAmxq0EXs+Y5i/by9CgzjO +2HjRe5PTse/hMxf3dKyp4zPE87UnxpoCCanAnYvZj2KvNdl+IPpbIzFg8jsd5hop +iO/BjXkTIVcp4JClB+gs94TCJxQaRBFEUn49Iffa7V1j5Vm2E6t6biG2tcgscDzv +b/x32HKCGKC05MiXBTiBJ7s42Jrz9qCrRKJqf0QBp0+fHmhk5FsBZ1YfStyQY8xl +xLyGolOU0REKnkaXGi2S0yHmUWMGzkPeByyLzDshcqgjw1Yv/ImftJoYe7zyp7cJ +eHkMRmu1ysCrP1TZv2s1tIbtYBGujFAVj1DDCuAO1ve+ISZwDBwJYuEGNKfH7kwq +RThASn9Cy9o/Kuo7f1DrZKunJMpSOtfor1zumxezQoYyzVZ8jC22q5FJcoUH7zEI +DQ/JmLOEw3uKyi9Qj3BgQDwkWm0oIJS1zg+q714PjTprGguHV1DThbzC46QomflZ +RowRACTTWcR3Lq2DguCZRfTZKWhitp4D8qlwTbEHgGuPAgMBAAE= +-----END PUBLIC KEY----- diff --git a/waltid-cli/src/jvmTest/resources/rsa_by_openssl_pvt_key.jwk b/waltid-cli/src/jvmTest/resources/rsa_by_openssl_pvt_key.jwk new file mode 100644 index 000000000..79c8501bb --- /dev/null +++ b/waltid-cli/src/jvmTest/resources/rsa_by_openssl_pvt_key.jwk @@ -0,0 +1 @@ +{"p":"x12f3gmshajc8bgOg6yuscy8TkvwDBO-rdK8qb_V8Fv21wXZxlr6jN9Qko5C6iLHLXl3MuWhtREHUTI9EZ6ZBXe2OmSHUouAaJp1F2m2wevWmYZl24Gc_4vls9jZlss_jggwFq4q2_bk145uBL9jE3iBuwvQmo1aZLI-uaxb8VT90nHSIG2_g171fzxrieJlzDFnpG3Ny9P7Wr5s0u31Te9d2os9-9S9CFB-s4ndJb1w_HJINKkbPDo4NZd1uId9","kty":"RSA","q":"xypHLxvz1CWIy5zOWbtrzg4xmOYfN5VY_I-1Yg5z_BPvEl3mDaeT_ZCGsx7uRlzvjTtgRqdbKAxpHr3Uph7L-jY3Zw45EHCF-wnZiy4nLLOAtHVSfp_XsyEEs7l4cJdRg3o72cH63ioVOjvxtlCqxpuz3p-WV81LCJRqO32OcbE_53NGtzlBEeA4558ak7v_7F0Spy-uXOe0Xp3GuPVpdA2STiIbEl_9giIm7R9kp8fJsFTFwVI_pJ92DystfiT7","d":"IHUpmoDqcOgto2RF7HTuYEGEQPKR-eoDe_A3ggyB7sBOJlvSC479_yytWKrD5-wUU1YEvXz2pno2WeqCGr191KCrpeHg3XjClDJgvrNY-aEoCgp8ZqMgY4z0WQ_nmgWRmpS39AzN7Y8Tj53oosI1rv8ryzlHPUUgJcJOTDLjYD-fKUDf7cv4mz_Lvn8qQ2t3lmLtPhZHSGsu2Dko5CGJaG61U_HeshN5X97QzxMbZyBwdLae3Xo5U9IAHUiAeuBxbBCBFcucy9DVwl_As5nqC0_3diD31sxZv3Esv1sGwlQDZVQHoTzkFOLGo_k9hhEXJ1p9Pao-yh1AUa7KLEebQ66rFqI-Bhj-08CvzQ7SoNH5wS9x5yL3SblB39A5JSmTNl2t7hFw6_1_NvRIr8P458Na2KNHrhROuy8ap7-3-YdGkHB4ll_cLRv_-4cpoJgLTjffi4PHs_g-HPp67ywtRSE4k652gqp9JPcGqMjA_ryI83bFaByB-e66oajVhtM9","e":"AQAB","qi":"CKy4nQIdf2aS-gAx-yrhSzd5Q7S7M7UO5036MvLkpCv_iha2hrmxFcnelOJO3HJ_6ljV79lTg-TJEq-sXsXp-bFeWMqj1TOgWXsTXaKPw4PUZ_fEx--CPC7XyNjnyz1Tao9RBtPM5c_38J6LZ_Z9Q4ELdFKcVmpEChN1fwJ2ht21ki2H6ihIQEGJNerxxijWXuKJlxascl05ZqqNkBXPsjs1VvWQhjoubr3UY0rGKf7tzLMi95-emIO3EuvuZwwJ","dp":"Z74RbaJNEzRe3K2xZ9WZBk6KgpfDbxVrONqbcB2yPyQr25Jg03YOQPYH4GuE6H92c_RsEaEqt6UH0Lm6y4tjB1RXECW1wT90b3pIiglpn5mQj00_fa0BvHzY5_BksbJL_SXHmFXDWbktNfoYyAGrlbs0jtfEEliR_CpAt6-4HGnktvihplxVtw_X4gDX2OVloY7n5sl4uKMzffHvgQdwicCQbyPb-kqmn0f71oNb_8KHo-X4KucAlCObkk-hY4el","dq":"OBWQvBRcAjabofLDLQOZJQpcLxlGWymkSGLTigxV3vtiDEMC4H97LiE_vTsNkCTllFjPELZZ9hogk_aS5kCv4gLYcR3RNe7p27p3Vzkk8PKPYMHU_DFY1WmL4GxvHQ2Pd725EuYMFfm1xpNQyq1Gme0Ipr074fe-lGjuzVfa_-sQ-sU8eaYWy8jfXWIxYr7DH7VJ5miH6kOZSDeX2UfKbVLRC02RYAhF5Bpn6cg4WQLqqcwFcB4QK_R3k2fuRKJ3","n":"mxq0EXs-Y5i_by9CgzjO2HjRe5PTse_hMxf3dKyp4zPE87UnxpoCCanAnYvZj2KvNdl-IPpbIzFg8jsd5hopiO_BjXkTIVcp4JClB-gs94TCJxQaRBFEUn49Iffa7V1j5Vm2E6t6biG2tcgscDzvb_x32HKCGKC05MiXBTiBJ7s42Jrz9qCrRKJqf0QBp0-fHmhk5FsBZ1YfStyQY8xlxLyGolOU0REKnkaXGi2S0yHmUWMGzkPeByyLzDshcqgjw1Yv_ImftJoYe7zyp7cJeHkMRmu1ysCrP1TZv2s1tIbtYBGujFAVj1DDCuAO1ve-ISZwDBwJYuEGNKfH7kwqRThASn9Cy9o_Kuo7f1DrZKunJMpSOtfor1zumxezQoYyzVZ8jC22q5FJcoUH7zEIDQ_JmLOEw3uKyi9Qj3BgQDwkWm0oIJS1zg-q714PjTprGguHV1DThbzC46QomflZRowRACTTWcR3Lq2DguCZRfTZKWhitp4D8qlwTbEHgGuP"} \ No newline at end of file diff --git a/waltid-cli/src/jvmTest/resources/rsa_by_openssl_pvt_key.pem b/waltid-cli/src/jvmTest/resources/rsa_by_openssl_pvt_key.pem new file mode 100644 index 000000000..a5b90650d --- /dev/null +++ b/waltid-cli/src/jvmTest/resources/rsa_by_openssl_pvt_key.pem @@ -0,0 +1,40 @@ +-----BEGIN PRIVATE KEY----- +MIIG/AIBADANBgkqhkiG9w0BAQEFAASCBuYwggbiAgEAAoIBgQCbGrQRez5jmL9v +L0KDOM7YeNF7k9Ox7+EzF/d0rKnjM8TztSfGmgIJqcCdi9mPYq812X4g+lsjMWDy +Ox3mGimI78GNeRMhVyngkKUH6Cz3hMInFBpEEURSfj0h99rtXWPlWbYTq3puIba1 +yCxwPO9v/HfYcoIYoLTkyJcFOIEnuzjYmvP2oKtEomp/RAGnT58eaGTkWwFnVh9K +3JBjzGXEvIaiU5TREQqeRpcaLZLTIeZRYwbOQ94HLIvMOyFyqCPDVi/8iZ+0mhh7 +vPKntwl4eQxGa7XKwKs/VNm/azW0hu1gEa6MUBWPUMMK4A7W974hJnAMHAli4QY0 +p8fuTCpFOEBKf0LL2j8q6jt/UOtkq6ckylI61+ivXO6bF7NChjLNVnyMLbarkUly +hQfvMQgND8mYs4TDe4rKL1CPcGBAPCRabSgglLXOD6rvXg+NOmsaC4dXUNOFvMLj +pCiZ+VlGjBEAJNNZxHcurYOC4JlF9NkpaGK2ngPyqXBNsQeAa48CAwEAAQKCAYAg +dSmagOpw6C2jZEXsdO5gQYRA8pH56gN78DeCDIHuwE4mW9ILjv3/LK1YqsPn7BRT +VgS9fPamejZZ6oIavX3UoKul4eDdeMKUMmC+s1j5oSgKCnxmoyBjjPRZD+eaBZGa +lLf0DM3tjxOPneiiwjWu/yvLOUc9RSAlwk5MMuNgP58pQN/ty/ibP8u+fypDa3eW +Yu0+FkdIay7YOSjkIYlobrVT8d6yE3lf3tDPExtnIHB0tp7dejlT0gAdSIB64HFs +EIEVy5zL0NXCX8CzmeoLT/d2IPfWzFm/cSy/WwbCVANlVAehPOQU4saj+T2GERcn +Wn09qj7KHUBRrsosR5tDrqsWoj4GGP7TwK/NDtKg0fnBL3HnIvdJuUHf0DklKZM2 +Xa3uEXDr/X829Eivw/jnw1rYo0euFE67Lxqnv7f5h0aQcHiWX9wtG//7hymgmAtO +N9+Lg8ez+D4c+nrvLC1FITiTrnaCqn0k9waoyMD+vIjzdsVoHIH57rqhqNWG0z0C +gcEAx12f3gmshajc8bgOg6yuscy8TkvwDBO+rdK8qb/V8Fv21wXZxlr6jN9Qko5C +6iLHLXl3MuWhtREHUTI9EZ6ZBXe2OmSHUouAaJp1F2m2wevWmYZl24Gc/4vls9jZ +lss/jggwFq4q2/bk145uBL9jE3iBuwvQmo1aZLI+uaxb8VT90nHSIG2/g171fzxr +ieJlzDFnpG3Ny9P7Wr5s0u31Te9d2os9+9S9CFB+s4ndJb1w/HJINKkbPDo4NZd1 +uId9AoHBAMcqRy8b89QliMuczlm7a84OMZjmHzeVWPyPtWIOc/wT7xJd5g2nk/2Q +hrMe7kZc7407YEanWygMaR691KYey/o2N2cOORBwhfsJ2YsuJyyzgLR1Un6f17Mh +BLO5eHCXUYN6O9nB+t4qFTo78bZQqsabs96fllfNSwiUajt9jnGxP+dzRrc5QRHg +OOefGpO7/+xdEqcvrlzntF6dxrj1aXQNkk4iGxJf/YIiJu0fZKfHybBUxcFSP6Sf +dg8rLX4k+wKBwGe+EW2iTRM0XtytsWfVmQZOioKXw28Vazjam3Adsj8kK9uSYNN2 +DkD2B+BrhOh/dnP0bBGhKrelB9C5usuLYwdUVxAltcE/dG96SIoJaZ+ZkI9NP32t +Abx82OfwZLGyS/0lx5hVw1m5LTX6GMgBq5W7NI7XxBJYkfwqQLevuBxp5Lb4oaZc +VbcP1+IA19jlZaGO5+bJeLijM33x74EHcInAkG8j2/pKpp9H+9aDW//Ch6Pl+Crn +AJQjm5JPoWOHpQKBwDgVkLwUXAI2m6Hywy0DmSUKXC8ZRlsppEhi04oMVd77YgxD +AuB/ey4hP707DZAk5ZRYzxC2WfYaIJP2kuZAr+IC2HEd0TXu6du6d1c5JPDyj2DB +1PwxWNVpi+Bsbx0Nj3e9uRLmDBX5tcaTUMqtRpntCKa9O+H3vpRo7s1X2v/rEPrF +PHmmFsvI311iMWK+wx+1SeZoh+pDmUg3l9lHym1S0QtNkWAIReQaZ+nIOFkC6qnM +BXAeECv0d5Nn7kSidwKBwAisuJ0CHX9mkvoAMfsq4Us3eUO0uzO1DudN+jLy5KQr +/4oWtoa5sRXJ3pTiTtxyf+pY1e/ZU4PkyRKvrF7F6fmxXljKo9UzoFl7E12ij8OD +1Gf3xMfvgjwu18jY58s9U2qPUQbTzOXP9/Cei2f2fUOBC3RSnFZqRAoTdX8Cdobd +tZIth+ooSEBBiTXq8cYo1l7iiZcWrHJdOWaqjZAVz7I7NVb1kIY6Lm691GNKxin+ +7cyzIvefnpiDtxLr7mcMCQ== +-----END PRIVATE KEY----- diff --git a/waltid-cli/src/jvmTest/resources/secp256k1_by_openssl_pub_key.jwk b/waltid-cli/src/jvmTest/resources/secp256k1_by_openssl_pub_key.jwk new file mode 100644 index 000000000..76654ac0b --- /dev/null +++ b/waltid-cli/src/jvmTest/resources/secp256k1_by_openssl_pub_key.jwk @@ -0,0 +1 @@ +{"kty":"EC","crv":"secp256k1","x":"D7KA0wd4qOX37HvlneDt1XdfV8fVRG2xWQGCjsO8s0c","y":"8WdPZDROGzCkKA36UUp6ZQgSfd0pzx1BUiULuQnhTHs"} \ No newline at end of file diff --git a/waltid-cli/src/jvmTest/resources/secp256k1_by_openssl_pub_key.pem b/waltid-cli/src/jvmTest/resources/secp256k1_by_openssl_pub_key.pem new file mode 100644 index 000000000..b19f8bb6b --- /dev/null +++ b/waltid-cli/src/jvmTest/resources/secp256k1_by_openssl_pub_key.pem @@ -0,0 +1,4 @@ +-----BEGIN EC PUBLIC KEY----- +MFYwEAYHKoZIzj0CAQYFK4EEAAoDQgAED7KA0wd4qOX37HvlneDt1XdfV8fVRG2x +WQGCjsO8s0fxZ09kNE4bMKQoDfpRSnplCBJ93SnPHUFSJQu5CeFMew== +-----END EC PUBLIC KEY----- diff --git a/waltid-cli/src/jvmTest/resources/secp256k1_by_openssl_pub_pvt_key.jwk b/waltid-cli/src/jvmTest/resources/secp256k1_by_openssl_pub_pvt_key.jwk new file mode 100644 index 000000000..79466e280 --- /dev/null +++ b/waltid-cli/src/jvmTest/resources/secp256k1_by_openssl_pub_pvt_key.jwk @@ -0,0 +1 @@ +{"kty":"EC","d":"kOSSmKKR1ztgHIMF6cbLDqPk4Cau6axPd86-SZeVFoY","crv":"secp256k1","x":"DbdohN6Kpm67iVqmvz8aR-tAtatk2SUjgB4tl3j3Nb4","y":"8i_kz3rn1GlAY0ZqAQGbbdbwYPH86Ytn9TGbZB4Vbpg"} \ No newline at end of file diff --git a/waltid-cli/src/jvmTest/resources/secp256k1_by_openssl_pub_pvt_key.pem b/waltid-cli/src/jvmTest/resources/secp256k1_by_openssl_pub_pvt_key.pem new file mode 100644 index 000000000..87448ddcc --- /dev/null +++ b/waltid-cli/src/jvmTest/resources/secp256k1_by_openssl_pub_pvt_key.pem @@ -0,0 +1,8 @@ +-----BEGIN EC PARAMETERS----- +BgUrgQQACg== +-----END EC PARAMETERS----- +-----BEGIN EC PRIVATE KEY----- +MHQCAQEEIJDkkpiikdc7YByDBenGyw6j5OAmrumsT3fOvkmXlRaGoAcGBSuBBAAK +oUQDQgAEDbdohN6Kpm67iVqmvz8aR+tAtatk2SUjgB4tl3j3Nb7yL+TPeufUaUBj +RmoBAZtt1vBg8fzpi2f1MZtkHhVumA== +-----END EC PRIVATE KEY----- diff --git a/waltid-cli/src/jvmTest/resources/secp256k1_by_openssl_pvt_key.pem b/waltid-cli/src/jvmTest/resources/secp256k1_by_openssl_pvt_key.pem new file mode 100644 index 000000000..6a24aeeab --- /dev/null +++ b/waltid-cli/src/jvmTest/resources/secp256k1_by_openssl_pvt_key.pem @@ -0,0 +1,5 @@ +-----BEGIN PRIVATE KEY----- +MIGEAgEAMBAGByqGSM49AgEGBSuBBAAKBG0wawIBAQQghItf3kprlQm9bYmnDKch +RxBRCWaQBhKi+b2sSjCxCKmhRANCAAQPsoDTB3io5ffse+Wd4O3Vd19Xx9VEbbFZ +AYKOw7yzR/FnT2Q0ThswpCgN+lFKemUIEn3dKc8dQVIlC7kJ4Ux7 +-----END PRIVATE KEY----- diff --git a/waltid-cli/src/jvmTest/resources/secp256k1_by_waltid_pvt_key.jwk b/waltid-cli/src/jvmTest/resources/secp256k1_by_waltid_pvt_key.jwk new file mode 100644 index 000000000..d6b9b470b --- /dev/null +++ b/waltid-cli/src/jvmTest/resources/secp256k1_by_waltid_pvt_key.jwk @@ -0,0 +1,8 @@ +{ + "kty": "EC", + "d": "mRKHNNlqkRxLtTBd4zSDtuPjV36NY7Kg6yusAIQlH_g", + "crv": "secp256k1", + "kid": "xxWVWbA0sgAAyeq3YyrA_-DQYbKtzmZJKeDn_qRwZYE", + "x": "9uDtgwPgXmV3QdQYNwdurDMptuW9nicx3vpqRzo69Zc", + "y": "nZRz7ZyUxIMslHlyouR0IdZ1Z2Sl5-_Q_2xYA_XupIA" +} \ No newline at end of file diff --git a/waltid-cli/src/jvmTest/resources/secp256k1_by_waltid_pvt_key.pem b/waltid-cli/src/jvmTest/resources/secp256k1_by_waltid_pvt_key.pem new file mode 100644 index 000000000..94973fbb0 --- /dev/null +++ b/waltid-cli/src/jvmTest/resources/secp256k1_by_waltid_pvt_key.pem @@ -0,0 +1,4 @@ +-----BEGIN EC PRIVATE KEY----- +MD4CAQAwEAYHKoZIzj0CAQYFK4EEAAoEJzAlAgEBBCCZEoc02WqRHEu1MF3jNIO2 +4+NXfo1jsqDrK6wAhCUf+A== +-----END EC PRIVATE KEY----- diff --git a/waltid-cli/waltid-cli-development.sh b/waltid-cli/waltid-cli-development.sh new file mode 100755 index 000000000..0e67e4662 --- /dev/null +++ b/waltid-cli/waltid-cli-development.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env sh + +# Launcher script which rebuilds the waltid-cli from source and executes the command supplied + +gradle --quiet installDist && build/install/waltid-cli/bin/waltid-cli "$@" diff --git a/waltid-cli/waltid-cli.sh b/waltid-cli/waltid-cli.sh new file mode 100755 index 000000000..9fcb2c4f7 --- /dev/null +++ b/waltid-cli/waltid-cli.sh @@ -0,0 +1,16 @@ +#!/usr/bin/env sh + +# Launcher script which builds the waltid-cli from source **once** and executes the command supplied +# If changes are made to the source then waltid-cli has to manually be rebuild (e.g. through waltid-cli-development.sh) + +if [ -f 'build/install/waltid-cli/bin/waltid-cli' ]; then + build/install/waltid-cli/bin/waltid-cli "$@" +else + echo "waltid-cli not yet build." + + echo "Running build..." + ../gradlew installDist + + echo "Trying to run CLI command after build..." + build/install/waltid-cli/bin/waltid-cli "$@" +fi diff --git a/waltid-crypto/build.gradle.kts b/waltid-crypto/build.gradle.kts index 7a362d93a..98b2249d3 100644 --- a/waltid-crypto/build.gradle.kts +++ b/waltid-crypto/build.gradle.kts @@ -28,7 +28,6 @@ suspendTransform { js { }*/ - useJvmDefault() useJsDefault() } @@ -101,8 +100,8 @@ kotlin { //implementation("dev.whyoleg.cryptography:cryptography-jdk:0.1.0") implementation("com.google.crypto.tink:tink:1.12.0") // for JOSE using Ed25519 - implementation("org.bouncycastle:bcprov-jdk18on:1.77") // for secp256k1 (which was removed with Java 17) - implementation("org.bouncycastle:bcpkix-jdk18on:1.77") // PEM import + implementation("org.bouncycastle:bcprov-lts8on:2.73.4") // for secp256k1 (which was removed with Java 17) + implementation("org.bouncycastle:bcpkix-lts8on:2.73.4") // PEM import // Ktor client implementation("io.ktor:ktor-client-cio:2.3.8") @@ -114,7 +113,7 @@ kotlin { implementation("com.nimbusds:nimbus-jose-jwt:9.37.3") // Multibase - implementation("com.github.multiformats:java-multibase:v1.1.1") +// implementation("com.github.multiformats:java-multibase:v1.1.1") } } val jvmTest by getting { @@ -132,7 +131,7 @@ kotlin { implementation(npm("jose", "4.14.4")) // Multibase - implementation(npm("multiformats", "12.1.2")) + // implementation(npm("multiformats", "12.1.2")) } } val jsTest by getting { @@ -180,10 +179,3 @@ extensions.getByType().apply { ) ) } - - -tasks.withType { - rejectVersionIf { - listOf("-beta", "-alpha", "-rc").any { it in candidate.version.lowercase() } || candidate.version.takeLast(4).contains("RC") - } -} diff --git a/waltid-crypto/src/commonMain/kotlin/id/walt/crypto/keys/Key.kt b/waltid-crypto/src/commonMain/kotlin/id/walt/crypto/keys/Key.kt index 86deb9e44..77325d208 100644 --- a/waltid-crypto/src/commonMain/kotlin/id/walt/crypto/keys/Key.kt +++ b/waltid-crypto/src/commonMain/kotlin/id/walt/crypto/keys/Key.kt @@ -1,7 +1,10 @@ package id.walt.crypto.keys +import id.walt.crypto.utils.JsonUtils.prettyJson import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.Serializable +import kotlinx.serialization.encodeToString +import kotlinx.serialization.json.Json import kotlinx.serialization.json.JsonClassDiscriminator import kotlinx.serialization.json.JsonObject import love.forte.plugin.suspendtrans.annotation.JsPromise @@ -42,6 +45,13 @@ abstract class Key { @JsPromise @JsExport.Ignore abstract suspend fun exportJWK(): String + @JvmBlocking + @JvmAsync + @JsPromise + @JsExport.Ignore + open suspend fun exportJWKPretty(): String = prettyJson.encodeToString(Json.parseToJsonElement(exportJWK())) + + @JvmBlocking @JvmAsync @JsPromise diff --git a/waltid-crypto/src/commonMain/kotlin/id/walt/crypto/keys/LocalKey.kt b/waltid-crypto/src/commonMain/kotlin/id/walt/crypto/keys/LocalKey.kt index 8fe1264a1..fdf023e88 100644 --- a/waltid-crypto/src/commonMain/kotlin/id/walt/crypto/keys/LocalKey.kt +++ b/waltid-crypto/src/commonMain/kotlin/id/walt/crypto/keys/LocalKey.kt @@ -8,6 +8,7 @@ expect class LocalKey(jwk: String?) : Key { override suspend fun getThumbprint(): String override suspend fun exportJWK(): String + override suspend fun exportJWKObject(): JsonObject override suspend fun exportPEM(): String diff --git a/waltid-crypto/src/commonMain/kotlin/id/walt/crypto/utils/Base58Utils.kt b/waltid-crypto/src/commonMain/kotlin/id/walt/crypto/utils/Base58Utils.kt new file mode 100644 index 000000000..4056ddb19 --- /dev/null +++ b/waltid-crypto/src/commonMain/kotlin/id/walt/crypto/utils/Base58Utils.kt @@ -0,0 +1,115 @@ +package id.walt.crypto.utils + +// https://github.com/komputing/KBase58/blob/master/kbase58/src/main/kotlin/org/komputing/kbase58/Base58.kt + +private const val ENCODED_ZERO = '1' + +private const val alphabet = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz" +private val alphabetIndices by lazy { + IntArray(128) { alphabet.indexOf(it.toChar()) } +} + +/** + * Encodes the bytes as a base58 string (no checksum is appended). + * + * @return the base58-encoded string + */ +fun ByteArray.encodeToBase58String(): String { + + val input = copyOf(size) // since we modify it in-place + if (input.isEmpty()) { + return "" + } + // Count leading zeros. + var zeros = 0 + while (zeros < input.size && input[zeros].toInt() == 0) { + ++zeros + } + // Convert base-256 digits to base-58 digits (plus conversion to ASCII characters) + val encoded = CharArray(input.size * 2) // upper bound + var outputStart = encoded.size + var inputStart = zeros + while (inputStart < input.size) { + encoded[--outputStart] = alphabet[divmod(input, inputStart.toUInt(), 256.toUInt(), 58.toUInt()).toInt()] + if (input[inputStart].toInt() == 0) { + ++inputStart // optimization - skip leading zeros + } + } + // Preserve exactly as many leading encoded zeros in output as there were leading zeros in data. + while (outputStart < encoded.size && encoded[outputStart] == ENCODED_ZERO) { + ++outputStart + } + while (--zeros >= 0) { + encoded[--outputStart] = ENCODED_ZERO + } + // Return encoded string (including encoded leading zeros). + return encoded.concatToString(outputStart, outputStart + (encoded.size - outputStart)) +} + +/** + * Decodes the base58 string into a [ByteArray] + * + * @return the decoded data bytes + * @throws NumberFormatException if the string is not a valid base58 string + */ +@Throws(NumberFormatException::class) +fun String.decodeBase58(): ByteArray { + if (isEmpty()) { + return ByteArray(0) + } + // Convert the base58-encoded ASCII chars to a base58 byte sequence (base58 digits). + val input58 = ByteArray(length) + for (i in 0 until length) { + val c = this[i] + val digit = if (c.toInt() < 128) alphabetIndices[c.toInt()] else -1 + if (digit < 0) { + throw NumberFormatException("Illegal character $c at position $i") + } + input58[i] = digit.toByte() + } + // Count leading zeros. + var zeros = 0 + while (zeros < input58.size && input58[zeros].toInt() == 0) { + ++zeros + } + // Convert base-58 digits to base-256 digits. + val decoded = ByteArray(length) + var outputStart = decoded.size + var inputStart = zeros + while (inputStart < input58.size) { + decoded[--outputStart] = divmod(input58, inputStart.toUInt(), 58.toUInt(), 256.toUInt()).toByte() + if (input58[inputStart].toInt() == 0) { + ++inputStart // optimization - skip leading zeros + } + } + // Ignore extra leading zeroes that were added during the calculation. + while (outputStart < decoded.size && decoded[outputStart].toInt() == 0) { + ++outputStart + } + // Return decoded data (including original number of leading zeros). + return decoded.copyOfRange(outputStart - zeros, decoded.size) +} + +/** + * Divides a number, represented as an array of bytes each containing a single digit + * in the specified base, by the given divisor. The given number is modified in-place + * to contain the quotient, and the return value is the remainder. + * + * @param number the number to divide + * @param firstDigit the index within the array of the first non-zero digit + * (this is used for optimization by skipping the leading zeros) + * @param base the base in which the number's digits are represented (up to 256) + * @param divisor the number to divide by (up to 256) + * @return the remainder of the division operation + */ +private fun divmod(number: ByteArray, firstDigit: UInt, base: UInt, divisor: UInt): UInt { + // this is just long division which accounts for the base of the input digits + var remainder = 0.toUInt() + for (i in firstDigit until number.size.toUInt()) { + val digit = number[i.toInt()].toUByte() + val temp = remainder * base + digit + number[i.toInt()] = (temp / divisor).toByte() + remainder = temp % divisor + } + return remainder +} diff --git a/waltid-crypto/src/commonMain/kotlin/id/walt/crypto/utils/JsonUtils.kt b/waltid-crypto/src/commonMain/kotlin/id/walt/crypto/utils/JsonUtils.kt index a5584fa35..837c8e9b6 100644 --- a/waltid-crypto/src/commonMain/kotlin/id/walt/crypto/utils/JsonUtils.kt +++ b/waltid-crypto/src/commonMain/kotlin/id/walt/crypto/utils/JsonUtils.kt @@ -9,6 +9,8 @@ import kotlin.js.JsName @JsExport object JsonUtils { + val prettyJson by lazy { Json { prettyPrint = true } } + fun Any?.toJsonElement(): JsonElement = when (this) { is JsonElement -> this diff --git a/waltid-crypto/src/commonMain/kotlin/id/walt/crypto/utils/MultiBaseUtils.kt b/waltid-crypto/src/commonMain/kotlin/id/walt/crypto/utils/MultiBaseUtils.kt index e8f32343a..f74844ddf 100644 --- a/waltid-crypto/src/commonMain/kotlin/id/walt/crypto/utils/MultiBaseUtils.kt +++ b/waltid-crypto/src/commonMain/kotlin/id/walt/crypto/utils/MultiBaseUtils.kt @@ -1,10 +1,48 @@ package id.walt.crypto.utils -expect object MultiBaseUtils { +object MultiBaseUtils { - fun convertRawKeyToMultiBase58Btc(key: ByteArray, code: UInt): String + fun convertRawKeyToMultiBase58Btc(key: ByteArray, code: UInt): String { + val codeVarInt = MultiCodecUtils.UVarInt(code) + val multicodecAndRawKey = ByteArray(key.size + codeVarInt.length) + codeVarInt.bytes.copyInto(multicodecAndRawKey) + key.copyInto(multicodecAndRawKey, codeVarInt.length) + return encodeMultiBase58Btc(multicodecAndRawKey) + } - fun convertMultiBase58BtcToRawKey(mb: String): ByteArray + fun convertMultiBase58BtcToRawKey(mb: String): ByteArray { + val bytes = decodeMultiBase58Btc(mb) + val code = MultiCodecUtils.UVarInt.fromBytes(bytes) + return bytes.drop(code.length).toByteArray() + } - fun decodeMultiBase58Btc(mb: String): ByteArray -} \ No newline at end of file + fun decodeMultiBase58Btc(mb: String): ByteArray = mb.substring(1).decodeBase58() + + fun encodeMultiBase58Btc(byteArray: ByteArray): String = 'z' + byteArray.encodeToBase58String() +} + +/* +For use with: implementation("com.github.multiformats:java-multibase:v1.1.1") + +actual object MultiBaseUtils { + + actual fun convertRawKeyToMultiBase58Btc(key: ByteArray, code: UInt): String { + val codeVarInt = MultiCodecUtils.UVarInt(code) + val multicodecAndRawKey = ByteArray(key.size + codeVarInt.length) + codeVarInt.bytes.copyInto(multicodecAndRawKey) + key.copyInto(multicodecAndRawKey, codeVarInt.length) + return encodeMultiBase58Btc(multicodecAndRawKey) + } + + actual fun convertMultiBase58BtcToRawKey(mb: String): ByteArray { + val bytes = decodeMultiBase58Btc(mb) + val code = MultiCodecUtils.UVarInt.fromBytes(bytes) + return bytes.drop(code.length).toByteArray() + } + + actual fun decodeMultiBase58Btc(mb: String): ByteArray = Multibase.decode(mb) + + private fun encodeMultiBase58Btc(byteArray: ByteArray): String = + Multibase.encode(Multibase.Base.Base58BTC, byteArray) +} + */ diff --git a/waltid-crypto/src/jsMain/kotlin/crypto/crypto.kt b/waltid-crypto/src/jsMain/kotlin/crypto/crypto.kt new file mode 100644 index 000000000..c651b9d01 --- /dev/null +++ b/waltid-crypto/src/jsMain/kotlin/crypto/crypto.kt @@ -0,0 +1,6 @@ +@JsModule("crypto") +@JsNonModule +external object crypto{ + fun sign(algorithm: String?, data: ByteArray, key: String): ByteArray + fun verify(algorithm: String?, data: ByteArray, key: String, signature: ByteArray): Boolean +} \ No newline at end of file diff --git a/waltid-crypto/src/jsMain/kotlin/id/walt/crypto/keys/LocalKey.js.kt b/waltid-crypto/src/jsMain/kotlin/id/walt/crypto/keys/LocalKey.js.kt index 2947d000c..77d5db665 100644 --- a/waltid-crypto/src/jsMain/kotlin/id/walt/crypto/keys/LocalKey.js.kt +++ b/waltid-crypto/src/jsMain/kotlin/id/walt/crypto/keys/LocalKey.js.kt @@ -2,9 +2,11 @@ package id.walt.crypto.keys import JWK import KeyLike +import crypto import id.walt.crypto.utils.ArrayUtils.toByteArray import id.walt.crypto.utils.JwsUtils.jwsAlg import id.walt.crypto.utils.PromiseUtils.await +import io.ktor.utils.io.core.* import jose import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable @@ -39,6 +41,7 @@ actual class LocalKey actual constructor( _internalJwk = JSON.parse(jwk!!) } } + @JsPromise @JsExport.Ignore override suspend fun init() { @@ -88,8 +91,11 @@ actual class LocalKey actual constructor( @JsPromise @JsExport.Ignore - actual override suspend fun exportJWK(): String = - JSON.stringify(_internalJwk) + actual override suspend fun exportJWK(): String = JSON.stringify(_internalJwk) + + @JsPromise + @JsExport.Ignore + override suspend fun exportJWKPretty(): String = JSON.stringify(_internalJwk, null, 4) @JsPromise @JsExport.Ignore @@ -109,7 +115,15 @@ actual class LocalKey actual constructor( @JsPromise @JsExport.Ignore actual override suspend fun signRaw(plaintext: ByteArray): ByteArray { - TODO("Not yet implemented") + check(hasPrivateKey) { "No private key is attached to this key!" } + return crypto.sign( + when (keyType) { + KeyType.Ed25519 -> null + else -> "sha256" + }, + plaintext, + exportPEM() + ) } /** @@ -135,7 +149,22 @@ actual class LocalKey actual constructor( @JsPromise @JsExport.Ignore actual override suspend fun verifyRaw(signed: ByteArray, detachedPlaintext: ByteArray?): Result { - TODO("Not yet implemented") + return runCatching { + val verified = crypto.verify( + when (keyType) { + KeyType.Ed25519 -> null + else -> "sha256" + }, + detachedPlaintext ?: signed, + getPublicKey().exportPEM(), + signed + ) + if (verified) { + "true".toByteArray() + } else { + throw IllegalArgumentException("Signature verification failed") + } + } } /** @@ -170,8 +199,7 @@ actual class LocalKey actual constructor( @JsPromise @JsExport.Ignore actual override suspend fun getPublicKeyRepresentation(): ByteArray { - - TODO("Not yet implemented") + return getPublicKey().exportPEM().toByteArray() } override val keyType: KeyType @@ -234,6 +262,7 @@ actual class LocalKey actual constructor( @JsPromise @JsExport.Ignore actual override suspend fun importJWK(jwk: String): Result = JsLocalKeyCreator.importJWK(jwk) + @JsPromise @JsExport.Ignore actual override suspend fun importPEM(pem: String): Result = JsLocalKeyCreator.importPEM(pem) diff --git a/waltid-crypto/src/jsMain/kotlin/id/walt/crypto/keys/LocalKeyGenerator.js.kt b/waltid-crypto/src/jsMain/kotlin/id/walt/crypto/keys/LocalKeyGenerator.js.kt index 6ccaf9120..b0088aa7c 100644 --- a/waltid-crypto/src/jsMain/kotlin/id/walt/crypto/keys/LocalKeyGenerator.js.kt +++ b/waltid-crypto/src/jsMain/kotlin/id/walt/crypto/keys/LocalKeyGenerator.js.kt @@ -1,5 +1,6 @@ package id.walt.crypto.keys +import JWK import KeyLike import id.walt.crypto.utils.JwsUtils.jwsAlg import id.walt.crypto.utils.PromiseUtils.await @@ -25,13 +26,20 @@ object JsLocalKeyCreator : LocalKeyCreator { @JsPromise @JsExport.Ignore override suspend fun importRawPublicKey(type: KeyType, rawPublicKey: ByteArray, metadata: LocalKeyMetadata): Key { - TODO("Not yet implemented") + val key: KeyLike = await(jose.importSPKI(rawPublicKey.decodeToString(), type.jwsAlg())) + return LocalKey(key).apply { init() } } @JsPromise @JsExport.Ignore override suspend fun importJWK(jwk: String): Result = - runCatching { LocalKey(await(jose.importJWK(JSON.parse(jwk))), JSON.parse(jwk)).apply { init() } } + runCatching { + var jsonJWK = JSON.parse(jwk) + while (jsonJWK::class == String::class) { + jsonJWK = JSON.parse(jsonJWK as String) + } + LocalKey(await(jose.importJWK(jsonJWK)), jsonJWK).apply { init() } + } /** diff --git a/waltid-crypto/src/jsMain/kotlin/id/walt/crypto/utils/MultiBaseUtils.kt b/waltid-crypto/src/jsMain/kotlin/id/walt/crypto/utils/MultiBaseUtils.kt deleted file mode 100644 index 810ea5c92..000000000 --- a/waltid-crypto/src/jsMain/kotlin/id/walt/crypto/utils/MultiBaseUtils.kt +++ /dev/null @@ -1,11 +0,0 @@ -package id.walt.crypto.utils - -@OptIn(ExperimentalJsExport::class) -@JsExport -actual object MultiBaseUtils { - actual fun convertRawKeyToMultiBase58Btc(key: ByteArray, code: UInt): String = TODO("Not yet implemented") - - actual fun convertMultiBase58BtcToRawKey(mb: String): ByteArray = TODO("Not yet implemented") - - actual fun decodeMultiBase58Btc(mb: String): ByteArray = TODO("Not yet implemented") -} diff --git a/waltid-crypto/src/jsMain/kotlin/multibase/multibase.kt b/waltid-crypto/src/jsMain/kotlin/multibase/multibase.kt new file mode 100644 index 000000000..257ab6da8 --- /dev/null +++ b/waltid-crypto/src/jsMain/kotlin/multibase/multibase.kt @@ -0,0 +1,8 @@ +import org.khronos.webgl.Uint8Array + +@JsModule("multibase") +@JsNonModule +external object multibase { + fun encode(base: String, data: ByteArray): ByteArray + fun decode(data: Uint8Array): ByteArray +} \ No newline at end of file diff --git a/waltid-crypto/src/jsTest/kotlin/KeySignTests.kt b/waltid-crypto/src/jsTest/kotlin/KeySignTests.kt new file mode 100644 index 000000000..aa07ca84c --- /dev/null +++ b/waltid-crypto/src/jsTest/kotlin/KeySignTests.kt @@ -0,0 +1,34 @@ +import id.walt.crypto.keys.LocalKey +import io.ktor.util.* +import io.ktor.utils.io.core.* +import kotlinx.coroutines.test.runTest +import kotlin.test.Test + +class KeySignTests { + @Test + fun signAndVerifyRaw() = runTest { + val privateKeyJsonString = """ + { + "kty": "RSA", + "kid": "288WlRQvku-zrHFmvcAW86jnTF3qsMoEUKEbteI2K4A", + "n": "qV-fGqTo8r6L52sIJpt44bxLkODaF0_wvIL_eYYDL55H-Ap-b1q4pd4YyZb7pARor_1mob6sMRnnAr5htmO1XucmKBEiNY-12zza0q9smjLm3-eNqq-8PgsEqBz4lU1YIBeQzsCR0NTa3J3OHfr-bADVystQeonSPoRLqSoO78oAtonQWLX1MUfS9778-ECcxlM21-JaUjqMD0nQR6wl8L6oWGcR7PjcjPQAyuS_ASTy7MO0SqunpkGzj_H7uFbK9Np_dLIOr9ZqrkCSdioA_PgDyk36E8ayuMnN1HDy4ak_Q7yEX4R_C75T0JxuuYio06hugwyREgOQNID-DVUoLw", + "e": "AQAB", + "d": "hndR21ddUYqRi9JfkDcSSzSwUX8R5jwjBaaCqLoKQX3J6VR7eHBv889VooXplheh_UaSeorkLb9Atd7ruF-EmKmuk1S28gr79-hiWa3H7MvIm647vGz0Z9VbhxQpDm9vLVtILbyYh1DVyRzHjOm9n4UyNmQfqolMjzF81_p6DUfpkMcDBJSlsTmRKMWPG0u8mFm8aB9ZftbryPO36QOny7g4_M8SZG1yxGTbypjyDTP9WqMmHpaC-66gLszjyxwEbVh69m-HsDEs7Qg9oMVG2FiwtDSXIApwfLk2v2Yk4-TeAD49rZzw-QcJ0yqPeVdq8cgyFOhwX-cPtQIm8X7AgQ", + "p": "0bNpJzzOOgzpqaWkb-5PuUgY9AedUsnze24AtukXaN9VY7e5BLYcbE11RGeyj8kkhpotvZQ6WrYEfvSkfxBvoVc1q86FXiqlpwmUL-_jO4BbgESOK9eaWP1iWmWNrZpqwdnIeF3VZHfCIoFxRV_Tb_Sp8UNSueFgCH6IVJlfwSE", + "q": "zsTarRYo9lLE8XvzpGzpjtrOHsnLuk2n5GXP6M2X89BL8yc8_5Fp99m_Em9vGAOhZBK9ActZuZEGSVVhfV1ImGw17tLyQZSCAvSzQpZSYpT9EDeZgn_oSorfUgMKppm1X4rl5Yz7lMR1khljdKt_X6gFA6ADL2h_ARK1bBRjr08", + "dp": "lOfqTmN-KXiL39xwdM7rq6zHk1lo3KXtEIOfXEMOTXjxQJrwdaj_a-Rg1g8wm6uAFVicDFeaTFmdvazothWsvwuXYAWJbMGp2YASyytz1wehcea8ceNqhbB_y6L7RQA2uKp2EQrIgcwMfcYe8d1G3eQFXP2qW7XvJHj9Q92ZQiE", + "dq": "E7TDOpfQE5nT10f-8n7Gy6yi1GBbIEhiZewmIoPlpYEGnAfzUlAjj1GbWkBwkBNYgFcg2FjvFjZyKO8QOYh4cL5vbXGBUSq8MVfs9b2p4Gdervr9kGhsVR5jJkfP7gzcMlzkiDoliAopQmFVDzuBCjbTM4M-inglEo8b508SKRU", + "qi": "aJsDBhxQFDbpQr20TjgxImwBslVP9xIauy3ncCmjHix6Fc1l51gL71V1OWGnXaStGfoWy0gKkUnJuU3_X_xA_QwzAXPJYa-juRlD8BxTf7rmR_HC-XiVdyNnkU3afHtK4nShS2EuN2EXOrYDrbQoA13_a6Itk_55vDpJ3jciwS8" + } + """.trimIndent() + val privateKeyResult = LocalKey.importJWK(privateKeyJsonString) + val privateKey = privateKeyResult.getOrThrow() + + val res = privateKey.signRaw("Hello world!".toByteArray()) + println((res.encodeBase64())) + + val res2 = privateKey.verifyRaw(res, "Hello world!".toByteArray()) + val res2Result = res2.getOrThrow() + println(res2Result.decodeToString()) + } +} \ No newline at end of file diff --git a/waltid-crypto/src/jvmMain/kotlin/id/walt/crypto/keys/LocalKey.jvm.kt b/waltid-crypto/src/jvmMain/kotlin/id/walt/crypto/keys/LocalKey.jvm.kt index ac082d08f..a3d8c9446 100644 --- a/waltid-crypto/src/jvmMain/kotlin/id/walt/crypto/keys/LocalKey.jvm.kt +++ b/waltid-crypto/src/jvmMain/kotlin/id/walt/crypto/keys/LocalKey.jvm.kt @@ -18,12 +18,18 @@ 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.bouncycastle.util.io.pem.PemObject +import org.bouncycastle.util.io.pem.PemWriter +import java.io.ByteArrayOutputStream import java.security.* import java.security.spec.PKCS8EncodedKeySpec import java.util.* +import kotlin.js.ExperimentalJsExport private val bouncyCastleProvider = BouncyCastleProvider() + +@Suppress("EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING") @Serializable @SerialName("local") actual class LocalKey actual constructor( @@ -57,11 +63,44 @@ actual class LocalKey actual constructor( } actual override suspend fun exportJWK(): String = _internalJwk.toJSONString() + actual override suspend fun exportJWKObject(): JsonObject = JsonObject(_internalJwk.toJSONObject().mapValues { JsonPrimitive(it.value as String) }) actual override suspend fun exportPEM(): String { - TODO("Not yet implemented") + val pemObjects = ArrayList() + + when (keyType) { + KeyType.secp256r1, KeyType.secp256k1 -> _internalJwk.toECKey().let { + if (hasPrivateKey) { + pemObjects.add(PemObject("PRIVATE KEY", it.toECPrivateKey().encoded)) + pemObjects.add(PemObject("PUBLIC KEY", getPublicKey()._internalJwk.toECKey().toECPublicKey().encoded)) + } else { + pemObjects.add(PemObject("PUBLIC KEY", it.toECPublicKey().encoded)) + } + } + + KeyType.Ed25519 -> throw NotImplementedError("Ed25519 keys cannot be exported as PEM yet.") + + KeyType.RSA -> _internalJwk.toRSAKey().let { + if (hasPrivateKey) { + pemObjects.add(PemObject("RSA PRIVATE KEY", it.toRSAPrivateKey().encoded)) + pemObjects.add(PemObject("RSA PUBLIC KEY", getPublicKey()._internalJwk.toRSAKey().toRSAPublicKey().encoded)) + } else { + pemObjects.add(PemObject("RSA PUBLIC KEY", it.toRSAPublicKey().encoded)) + } + } + } + + val pem = ByteArrayOutputStream().apply { + PemWriter(writer()).use { + pemObjects.forEach { pemObject -> + it.writeObject(pemObject) + } + } + }.toByteArray().toString(Charsets.UTF_8) + + return pem } private val _internalSigner: JWSSigner by lazy { @@ -220,6 +259,9 @@ actual class LocalKey actual constructor( } actual companion object : LocalKeyCreator { + + val prettyJson = Json { prettyPrint = true } + actual override suspend fun generate(type: KeyType, metadata: LocalKeyMetadata): LocalKey = JvmLocalKeyCreator.generate(type, metadata) diff --git a/waltid-crypto/src/jvmMain/kotlin/id/walt/crypto/utils/MultiBaseUtils.kt b/waltid-crypto/src/jvmMain/kotlin/id/walt/crypto/utils/MultiBaseUtils.kt deleted file mode 100644 index 6d349b0aa..000000000 --- a/waltid-crypto/src/jvmMain/kotlin/id/walt/crypto/utils/MultiBaseUtils.kt +++ /dev/null @@ -1,25 +0,0 @@ -package id.walt.crypto.utils - -import io.ipfs.multibase.Multibase - -actual object MultiBaseUtils { - - actual fun convertRawKeyToMultiBase58Btc(key: ByteArray, code: UInt): String { - val codeVarInt = MultiCodecUtils.UVarInt(code) - val multicodecAndRawKey = ByteArray(key.size + codeVarInt.length) - codeVarInt.bytes.copyInto(multicodecAndRawKey) - key.copyInto(multicodecAndRawKey, codeVarInt.length) - return encodeMultiBase58Btc(multicodecAndRawKey) - } - - actual fun convertMultiBase58BtcToRawKey(mb: String): ByteArray { - val bytes = decodeMultiBase58Btc(mb) - val code = MultiCodecUtils.UVarInt.fromBytes(bytes) - return bytes.drop(code.length).toByteArray() - } - - actual fun decodeMultiBase58Btc(mb: String): ByteArray = Multibase.decode(mb) - - private fun encodeMultiBase58Btc(byteArray: ByteArray): String = - Multibase.encode(Multibase.Base.Base58BTC, byteArray) -} \ No newline at end of file diff --git a/waltid-crypto/src/jvmTest/kotlin/TestUtils.kt b/waltid-crypto/src/jvmTest/kotlin/TestUtils.kt index afbb2a72a..5fd2e6f2b 100644 --- a/waltid-crypto/src/jvmTest/kotlin/TestUtils.kt +++ b/waltid-crypto/src/jvmTest/kotlin/TestUtils.kt @@ -1,4 +1,5 @@ import java.io.File +import java.net.URLDecoder object TestUtils { fun loadJwkLocal(filename: String): String = loadResource("jwk/$filename") @@ -6,7 +7,9 @@ object TestUtils { fun loadSerializedLocal(filename: String): String = loadResource("serialized/local/$filename") fun loadSerializedTse(filename: String): String = loadResource("serialized/tse/$filename") fun loadResource(relativePath: String): String = - this::class.java.classLoader.getResource(relativePath)!!.path.let { File(it).readText() } + URLDecoder.decode(this::class.java.classLoader.getResource(relativePath)!!.path, "UTF-8") + .let { File(it).readText() } fun loadResourceBytes(relativePath: String): ByteArray = - this::class.java.classLoader.getResource(relativePath)!!.path.let { File(it).readBytes() } + URLDecoder.decode(this::class.java.classLoader.getResource(relativePath)!!.path, "UTF-8") + .let { File(it).readBytes() } } \ No newline at end of file diff --git a/waltid-did/README.md b/waltid-did/README.md index a4e6b63ae..831089a2b 100644 --- a/waltid-did/README.md +++ b/waltid-did/README.md @@ -79,7 +79,7 @@ val key = LocalKey.generate(KeyType.Ed25519) val options = DidKeyCreateOptions( useJwkJcsPub = true ) -val didResult = DidService.register( +val didResult = DidService.registerByKey( method = "key", key = key, options = options diff --git a/waltid-did/build.gradle.kts b/waltid-did/build.gradle.kts index 4734e0b7f..b4c21f922 100644 --- a/waltid-did/build.gradle.kts +++ b/waltid-did/build.gradle.kts @@ -1,10 +1,16 @@ import com.github.benmanes.gradle.versions.updates.DependencyUpdatesTask +import love.forte.plugin.suspendtrans.ClassInfo +import love.forte.plugin.suspendtrans.SuspendTransformConfiguration +import love.forte.plugin.suspendtrans.TargetPlatform +import love.forte.plugin.suspendtrans.gradle.SuspendTransformGradleExtension + plugins { kotlin("multiplatform") kotlin("plugin.serialization") id("maven-publish") id("com.github.ben-manes.versions") + id("love.forte.plugin.suspend-transform") version "0.6.0" } group = "id.walt.did" @@ -19,6 +25,18 @@ java { targetCompatibility = JavaVersion.VERSION_15 } +suspendTransform { + enabled = true + includeRuntime = true + /*jvm { + + } + js { + + }*/ + useJsDefault() +} + kotlin { jvmToolchain(15) @@ -72,6 +90,9 @@ kotlin { // Crypto api(project(":waltid-crypto")) + // Encodings + implementation("net.thauvin.erik.urlencoder:urlencoder-lib:1.4.0") + // Logging implementation("io.github.oshai:kotlin-logging:6.0.3") @@ -95,7 +116,7 @@ kotlin { implementation("io.github.erdtman:java-json-canonicalization:1.1") // Multiformat - implementation("com.github.multiformats:java-multibase:v1.1.1") +// implementation("com.github.multiformats:java-multibase:v1.1.1") } } val jvmTest by getting { @@ -145,10 +166,12 @@ kotlin { } } - - -tasks.withType { - rejectVersionIf { - listOf("-beta", "-alpha", "-rc").any { it in candidate.version.lowercase() } || candidate.version.takeLast(4).contains("RC") - } +extensions.getByType().apply { + transformers[TargetPlatform.JS] = mutableListOf( + SuspendTransformConfiguration.jsPromiseTransformer.copy( + copyAnnotationExcludes = listOf( + ClassInfo("kotlin.js", "JsExport.Ignore") + ) + ) + ) } diff --git a/waltid-did/src/commonMain/kotlin/id/walt/did/dids/DidManager.kt b/waltid-did/src/commonMain/kotlin/id/walt/did/dids/DidManager.kt index 392d12e92..8d828b6f2 100644 --- a/waltid-did/src/commonMain/kotlin/id/walt/did/dids/DidManager.kt +++ b/waltid-did/src/commonMain/kotlin/id/walt/did/dids/DidManager.kt @@ -1,5 +1,10 @@ package id.walt.did.dids +import kotlin.js.ExperimentalJsExport +import kotlin.js.JsExport + +@ExperimentalJsExport +@JsExport interface DidManager { fun resolve(did: String) diff --git a/waltid-did/src/commonMain/kotlin/id/walt/did/dids/DidService.kt b/waltid-did/src/commonMain/kotlin/id/walt/did/dids/DidService.kt index 559ee489a..622d38632 100644 --- a/waltid-did/src/commonMain/kotlin/id/walt/did/dids/DidService.kt +++ b/waltid-did/src/commonMain/kotlin/id/walt/did/dids/DidService.kt @@ -12,8 +12,15 @@ import id.walt.did.dids.resolver.DidResolverRegistrations import id.walt.did.dids.resolver.LocalResolver import io.github.oshai.kotlinlogging.KotlinLogging import kotlinx.serialization.json.JsonObject +import love.forte.plugin.suspendtrans.annotation.JsPromise +import love.forte.plugin.suspendtrans.annotation.JvmAsync +import love.forte.plugin.suspendtrans.annotation.JvmBlocking +import kotlin.js.ExperimentalJsExport +import kotlin.js.JsExport @Suppress("OPT_IN_USAGE") +@ExperimentalJsExport +@JsExport object DidService { private val log = KotlinLogging.logger {} @@ -47,6 +54,10 @@ object DidService { } } + @JvmBlocking + @JvmAsync + @JsPromise + @JsExport.Ignore suspend fun init() { registerAllResolvers(DidResolverRegistrations.didResolvers) registerAllRegistrars(DidRegistrarRegistrations.didRegistrars) @@ -58,6 +69,10 @@ object DidService { log.debug { "INIT -> REGISTRARS: $registrarMethods" } } + @JvmBlocking + @JvmAsync + @JsPromise + @JsExport.Ignore suspend fun minimalInit() { registerAllResolvers(setOf(LocalResolver())) registerAllRegistrars(setOf(LocalRegistrar())) @@ -70,6 +85,10 @@ object DidService { fun registerResolverForMethod(method: String, resolver: DidResolver) = resolverMethods.put(method, resolver) fun registerRegistrarForMethod(method: String, registrar: DidRegistrar) = registrarMethods.put(method, registrar) + @JvmBlocking + @JvmAsync + @JsPromise + @JsExport.Ignore suspend fun updateResolversForMethods() { didResolvers.forEach { resolver -> val methods = resolver.getSupportedMethods() @@ -83,6 +102,10 @@ object DidService { } } + @JvmBlocking + @JvmAsync + @JsPromise + @JsExport.Ignore suspend fun updateRegistrarsForMethods() { didRegistrars.forEach { registrar -> val methods = registrar.getSupportedMethods() @@ -107,18 +130,34 @@ object DidService { } /* - Did methods - */ + @JvmBlocking + @JvmAsync + @JsPromise + @JsExport.Ignore suspend fun resolve(did: String): Result = getResolverForDid(did).resolve(did) + @JvmBlocking + @JvmAsync + @JsPromise + @JsExport.Ignore suspend fun resolveToKey(did: String): Result = getResolverForDid(did).resolveToKey(did) private fun getRegistrarForMethod(method: String): DidRegistrar = registrarMethods[method] ?: throw IllegalArgumentException("No registrar for did method: $method") + @JvmBlocking + @JvmAsync + @JsPromise + @JsExport.Ignore suspend fun register(options: DidCreateOptions) = getRegistrarForMethod(options.method).create(options) + @JvmBlocking + @JvmAsync + @JsPromise + @JsExport.Ignore suspend fun registerByKey( method: String, key: Key, options: DidCreateOptions = DidCreateOptions(method, emptyMap()) ): DidResult = getRegistrarForMethod(method).createByKey(key, options) diff --git a/waltid-did/src/commonMain/kotlin/id/walt/did/dids/DidUtils.kt b/waltid-did/src/commonMain/kotlin/id/walt/did/dids/DidUtils.kt index 38bd4d451..f61190de4 100644 --- a/waltid-did/src/commonMain/kotlin/id/walt/did/dids/DidUtils.kt +++ b/waltid-did/src/commonMain/kotlin/id/walt/did/dids/DidUtils.kt @@ -1,5 +1,10 @@ package id.walt.did.dids +import kotlin.js.ExperimentalJsExport +import kotlin.js.JsExport + +@ExperimentalJsExport +@JsExport object DidUtils { private const val PATTERN = "^did:([a-z]+):(.+)" fun methodFromDid(did: String) = did.removePrefix("did:").substringBefore(":") diff --git a/waltid-did/src/commonMain/kotlin/id/walt/did/dids/document/DidCheqdDocument.kt b/waltid-did/src/commonMain/kotlin/id/walt/did/dids/document/DidCheqdDocument.kt index 7ab5b96ce..ca018dd71 100644 --- a/waltid-did/src/commonMain/kotlin/id/walt/did/dids/document/DidCheqdDocument.kt +++ b/waltid-did/src/commonMain/kotlin/id/walt/did/dids/document/DidCheqdDocument.kt @@ -9,7 +9,12 @@ import kotlinx.serialization.json.Json import kotlinx.serialization.json.JsonObject import kotlinx.serialization.json.encodeToJsonElement import kotlinx.serialization.json.jsonObject +import kotlin.js.ExperimentalJsExport +import kotlin.js.JsExport +import kotlin.js.JsName +@ExperimentalJsExport +@JsExport @OptIn(ExperimentalSerializationApi::class) @Serializable data class DidCheqdDocument( @@ -38,6 +43,7 @@ data class DidCheqdDocument( fun toMap() = Json.encodeToJsonElement(this).jsonObject.toMap() + @JsName("secondaryConstructor") constructor(didDoc: DidDocument, jwk: JsonObject? = null) : this( context = DEFAULT_CONTEXT, id = didDoc.id, diff --git a/waltid-did/src/commonMain/kotlin/id/walt/did/dids/document/DidDocument.kt b/waltid-did/src/commonMain/kotlin/id/walt/did/dids/document/DidDocument.kt index 7887c51a0..763821db3 100644 --- a/waltid-did/src/commonMain/kotlin/id/walt/did/dids/document/DidDocument.kt +++ b/waltid-did/src/commonMain/kotlin/id/walt/did/dids/document/DidDocument.kt @@ -4,7 +4,12 @@ import id.walt.crypto.utils.JsonUtils.printAsJson import kotlinx.serialization.Serializable import kotlinx.serialization.json.JsonElement import kotlinx.serialization.json.JsonObject +import kotlin.js.ExperimentalJsExport +import kotlin.js.JsExport +import kotlin.js.JsName +@ExperimentalJsExport +@JsExport @Serializable class DidDocument( private val content: Map @@ -16,6 +21,7 @@ class DidDocument( /** * From JsonObject */ + @JsName("secondaryConstructor") constructor(jsonObject: JsonObject) : this(jsonObject.toMap()) /** diff --git a/waltid-did/src/commonMain/kotlin/id/walt/did/dids/document/DidJwkDocument.kt b/waltid-did/src/commonMain/kotlin/id/walt/did/dids/document/DidJwkDocument.kt index 5f24e32fc..85d374e1b 100644 --- a/waltid-did/src/commonMain/kotlin/id/walt/did/dids/document/DidJwkDocument.kt +++ b/waltid-did/src/commonMain/kotlin/id/walt/did/dids/document/DidJwkDocument.kt @@ -9,6 +9,9 @@ import kotlinx.serialization.json.Json import kotlinx.serialization.json.JsonObject import kotlinx.serialization.json.encodeToJsonElement import kotlinx.serialization.json.jsonObject +import kotlin.js.ExperimentalJsExport +import kotlin.js.JsExport +import kotlin.js.JsName /* * if JWK has `use`="sig", no `keyAgreement` is included @@ -20,6 +23,8 @@ import kotlinx.serialization.json.jsonObject * update & deactivate not supported */ +@ExperimentalJsExport +@JsExport @OptIn(ExperimentalSerializationApi::class) @Serializable data class DidJwkDocument( @@ -48,6 +53,7 @@ data class DidJwkDocument( fun toMap() = Json.encodeToJsonElement(this).jsonObject.toMap() + @JsName("secondaryConstructor") constructor(did: String, didJwk: JsonObject) : this( context = DEFAULT_CONTEXT, id = did, diff --git a/waltid-did/src/commonMain/kotlin/id/walt/did/dids/document/DidKeyDocument.kt b/waltid-did/src/commonMain/kotlin/id/walt/did/dids/document/DidKeyDocument.kt index e7db25666..c38fe312a 100644 --- a/waltid-did/src/commonMain/kotlin/id/walt/did/dids/document/DidKeyDocument.kt +++ b/waltid-did/src/commonMain/kotlin/id/walt/did/dids/document/DidKeyDocument.kt @@ -8,7 +8,12 @@ import kotlinx.serialization.json.Json import kotlinx.serialization.json.JsonObject import kotlinx.serialization.json.encodeToJsonElement import kotlinx.serialization.json.jsonObject +import kotlin.js.ExperimentalJsExport +import kotlin.js.JsExport +import kotlin.js.JsName +@ExperimentalJsExport +@JsExport @OptIn(ExperimentalSerializationApi::class) @Serializable data class DidKeyDocument( @@ -37,10 +42,11 @@ data class DidKeyDocument( fun toMap() = Json.encodeToJsonElement(this).jsonObject.toMap() + @JsName("secondaryConstructor") constructor(did: String, identifier: String, didKey: JsonObject) : this( context = DEFAULT_CONTEXT, id = did, - verificationMethod = listOf(VerificationMethod(did, "JsonWebKey2020", did, didKey)), + verificationMethod = listOf(VerificationMethod("$did#$identifier", "JsonWebKey2020", did, didKey)), assertionMethod = listOf("$did#$identifier"), authentication = listOf("$did#$identifier"), diff --git a/waltid-did/src/commonMain/kotlin/id/walt/did/dids/document/DidWebDocument.kt b/waltid-did/src/commonMain/kotlin/id/walt/did/dids/document/DidWebDocument.kt index d596a9997..b1894d550 100644 --- a/waltid-did/src/commonMain/kotlin/id/walt/did/dids/document/DidWebDocument.kt +++ b/waltid-did/src/commonMain/kotlin/id/walt/did/dids/document/DidWebDocument.kt @@ -8,7 +8,12 @@ import kotlinx.serialization.json.Json import kotlinx.serialization.json.JsonObject import kotlinx.serialization.json.encodeToJsonElement import kotlinx.serialization.json.jsonObject +import kotlin.js.ExperimentalJsExport +import kotlin.js.JsExport +import kotlin.js.JsName +@ExperimentalJsExport +@JsExport @OptIn(ExperimentalSerializationApi::class) @Serializable data class DidWebDocument( @@ -37,10 +42,11 @@ data class DidWebDocument( fun toMap() = Json.encodeToJsonElement(this).jsonObject.toMap() + @JsName("secondaryConstructor") constructor(did: String, keyId: String, didKey: JsonObject) : this( context = DEFAULT_CONTEXT, id = did, - verificationMethod = listOf(VerificationMethod(did, "JsonWebKey2020", did, didKey)), + verificationMethod = listOf(VerificationMethod("$did#$keyId", "JsonWebKey2020", did, didKey)), assertionMethod = listOf("$did#$keyId"), authentication = listOf("$did#$keyId"), diff --git a/waltid-did/src/commonMain/kotlin/id/walt/did/dids/registrar/DidRegistrar.kt b/waltid-did/src/commonMain/kotlin/id/walt/did/dids/registrar/DidRegistrar.kt index abbe21279..ce957f2e6 100644 --- a/waltid-did/src/commonMain/kotlin/id/walt/did/dids/registrar/DidRegistrar.kt +++ b/waltid-did/src/commonMain/kotlin/id/walt/did/dids/registrar/DidRegistrar.kt @@ -2,15 +2,42 @@ package id.walt.did.dids.registrar import id.walt.crypto.keys.Key import id.walt.did.dids.registrar.dids.DidCreateOptions +import love.forte.plugin.suspendtrans.annotation.JsPromise +import love.forte.plugin.suspendtrans.annotation.JvmAsync +import love.forte.plugin.suspendtrans.annotation.JvmBlocking +import kotlin.js.ExperimentalJsExport +import kotlin.js.JsExport +@ExperimentalJsExport +@JsExport interface DidRegistrar { val name: String + @JvmBlocking + @JvmAsync + @JsPromise + @JsExport.Ignore suspend fun getSupportedMethods(): Result> + @JvmBlocking + @JvmAsync + @JsPromise + @JsExport.Ignore suspend fun create(options: DidCreateOptions): DidResult + @JvmBlocking + @JvmAsync + @JsPromise + @JsExport.Ignore suspend fun createByKey(key: Key, options: DidCreateOptions): DidResult + @JvmBlocking + @JvmAsync + @JsPromise + @JsExport.Ignore suspend fun update() + @JvmBlocking + @JvmAsync + @JsPromise + @JsExport.Ignore suspend fun delete() } diff --git a/waltid-did/src/commonMain/kotlin/id/walt/did/dids/registrar/DidRegistrarRegistrations.kt b/waltid-did/src/commonMain/kotlin/id/walt/did/dids/registrar/DidRegistrarRegistrations.kt index d16e471c9..c8563343d 100644 --- a/waltid-did/src/commonMain/kotlin/id/walt/did/dids/registrar/DidRegistrarRegistrations.kt +++ b/waltid-did/src/commonMain/kotlin/id/walt/did/dids/registrar/DidRegistrarRegistrations.kt @@ -1,5 +1,10 @@ package id.walt.did.dids.registrar +import kotlin.js.ExperimentalJsExport +import kotlin.js.JsExport + +@ExperimentalJsExport +@JsExport object DidRegistrarRegistrations { val didRegistrars = setOf( diff --git a/waltid-did/src/commonMain/kotlin/id/walt/did/dids/registrar/DidResult.kt b/waltid-did/src/commonMain/kotlin/id/walt/did/dids/registrar/DidResult.kt index 92d097d93..3b332ac44 100644 --- a/waltid-did/src/commonMain/kotlin/id/walt/did/dids/registrar/DidResult.kt +++ b/waltid-did/src/commonMain/kotlin/id/walt/did/dids/registrar/DidResult.kt @@ -2,7 +2,11 @@ package id.walt.did.dids.registrar import id.walt.did.dids.document.DidDocument import kotlinx.serialization.Serializable +import kotlin.js.ExperimentalJsExport +import kotlin.js.JsExport +@ExperimentalJsExport +@JsExport @Serializable data class DidResult( val did: String, diff --git a/waltid-did/src/commonMain/kotlin/id/walt/did/dids/registrar/LocalRegistrar.kt b/waltid-did/src/commonMain/kotlin/id/walt/did/dids/registrar/LocalRegistrar.kt index dbd2248c6..eadb5a15d 100644 --- a/waltid-did/src/commonMain/kotlin/id/walt/did/dids/registrar/LocalRegistrar.kt +++ b/waltid-did/src/commonMain/kotlin/id/walt/did/dids/registrar/LocalRegistrar.kt @@ -7,7 +7,14 @@ import id.walt.did.dids.registrar.local.cheqd.DidCheqdRegistrar import id.walt.did.dids.registrar.local.jwk.DidJwkRegistrar import id.walt.did.dids.registrar.local.key.DidKeyRegistrar import id.walt.did.dids.registrar.local.web.DidWebRegistrar +import love.forte.plugin.suspendtrans.annotation.JsPromise +import love.forte.plugin.suspendtrans.annotation.JvmAsync +import love.forte.plugin.suspendtrans.annotation.JvmBlocking +import kotlin.js.ExperimentalJsExport +import kotlin.js.JsExport +@ExperimentalJsExport +@JsExport class LocalRegistrar : DidRegistrar { override val name = "walt.id local registrar" @@ -18,23 +25,43 @@ class LocalRegistrar : DidRegistrar { DidCheqdRegistrar(), ).associateBy { it.method } + @JvmBlocking + @JvmAsync + @JsPromise + @JsExport.Ignore override suspend fun getSupportedMethods() = Result.success(setOf("key", "jwk", "web", "cheqd" /*"ebsi",*/)) //override suspend fun getSupportedMethods() = Result.success(registrarMethods.values.toSet()) private fun getRegistrarForMethod(method: String) = registrarMethods[method] ?: throw IllegalArgumentException("No local registrar for method: $method") + @JvmBlocking + @JvmAsync + @JsPromise + @JsExport.Ignore override suspend fun create(options: DidCreateOptions): DidResult = getRegistrarForMethod(options.method).register(options) + @JvmBlocking + @JvmAsync + @JsPromise + @JsExport.Ignore override suspend fun createByKey(key: Key, options: DidCreateOptions): DidResult = getRegistrarForMethod(options.method).registerByKey(key, options) + @JvmBlocking + @JvmAsync + @JsPromise + @JsExport.Ignore override suspend fun update() { TODO("Not yet implemented") } + @JvmBlocking + @JvmAsync + @JsPromise + @JsExport.Ignore override suspend fun delete() { TODO("Not yet implemented") } diff --git a/waltid-did/src/commonMain/kotlin/id/walt/did/dids/registrar/UniregistrarRegistrar.kt b/waltid-did/src/commonMain/kotlin/id/walt/did/dids/registrar/UniregistrarRegistrar.kt index cdc48915c..ba67edcf4 100644 --- a/waltid-did/src/commonMain/kotlin/id/walt/did/dids/registrar/UniregistrarRegistrar.kt +++ b/waltid-did/src/commonMain/kotlin/id/walt/did/dids/registrar/UniregistrarRegistrar.kt @@ -12,7 +12,14 @@ import io.ktor.http.* import io.ktor.serialization.kotlinx.json.* import kotlinx.serialization.json.JsonArray import kotlinx.serialization.json.jsonPrimitive +import love.forte.plugin.suspendtrans.annotation.JsPromise +import love.forte.plugin.suspendtrans.annotation.JvmAsync +import love.forte.plugin.suspendtrans.annotation.JvmBlocking +import kotlin.js.ExperimentalJsExport +import kotlin.js.JsExport +@ExperimentalJsExport +@JsExport class UniregistrarRegistrar : DidRegistrar { @Suppress("MemberVisibilityCanBePrivate") @@ -35,6 +42,10 @@ class UniregistrarRegistrar : DidRegistrar { } } + @JvmBlocking + @JvmAsync + @JsPromise + @JsExport.Ignore override suspend fun getSupportedMethods() = runCatching { lazyOf(getMethods()).value } private suspend fun getMethods(): Set = @@ -43,20 +54,36 @@ class UniregistrarRegistrar : DidRegistrar { .map { it.jsonPrimitive.content } .toSet() + @JvmBlocking + @JvmAsync + @JsPromise + @JsExport.Ignore override suspend fun create(options: DidCreateOptions): DidResult { return DidResult("TODO" /* TODO */, http.post("$registrarUrl/create?method=${options.method}") { setBody(options.options) }.body()) } + @JvmBlocking + @JvmAsync + @JsPromise + @JsExport.Ignore override suspend fun createByKey(key: Key, options: DidCreateOptions): DidResult { TODO("Not yet implemented") } + @JvmBlocking + @JvmAsync + @JsPromise + @JsExport.Ignore override suspend fun update() { TODO("Not yet implemented") } + @JvmBlocking + @JvmAsync + @JsPromise + @JsExport.Ignore override suspend fun delete() { TODO("Not yet implemented") } diff --git a/waltid-did/src/commonMain/kotlin/id/walt/did/dids/registrar/dids/DidBtcrCreateOptions.kt b/waltid-did/src/commonMain/kotlin/id/walt/did/dids/registrar/dids/DidBtcrCreateOptions.kt index 0a018a01b..bf1ed7eb2 100644 --- a/waltid-did/src/commonMain/kotlin/id/walt/did/dids/registrar/dids/DidBtcrCreateOptions.kt +++ b/waltid-did/src/commonMain/kotlin/id/walt/did/dids/registrar/dids/DidBtcrCreateOptions.kt @@ -1,5 +1,10 @@ package id.walt.did.dids.registrar.dids +import kotlin.js.ExperimentalJsExport +import kotlin.js.JsExport + +@ExperimentalJsExport +@JsExport class DidBtcrCreateOptions(chain: String) : DidCreateOptions( method = "btcr", options = options("chain" to chain) diff --git a/waltid-did/src/commonMain/kotlin/id/walt/did/dids/registrar/dids/DidCheqdCreateOptions.kt b/waltid-did/src/commonMain/kotlin/id/walt/did/dids/registrar/dids/DidCheqdCreateOptions.kt index 4b6e858f3..847c5b44f 100644 --- a/waltid-did/src/commonMain/kotlin/id/walt/did/dids/registrar/dids/DidCheqdCreateOptions.kt +++ b/waltid-did/src/commonMain/kotlin/id/walt/did/dids/registrar/dids/DidCheqdCreateOptions.kt @@ -1,5 +1,10 @@ package id.walt.did.dids.registrar.dids +import kotlin.js.ExperimentalJsExport +import kotlin.js.JsExport + +@ExperimentalJsExport +@JsExport class DidCheqdCreateOptions(network: String) : DidCreateOptions( method = "cheqd", options = mapOf("network" to network) diff --git a/waltid-did/src/commonMain/kotlin/id/walt/did/dids/registrar/dids/DidCreateOptions.kt b/waltid-did/src/commonMain/kotlin/id/walt/did/dids/registrar/dids/DidCreateOptions.kt index 1e3882118..78b95e756 100644 --- a/waltid-did/src/commonMain/kotlin/id/walt/did/dids/registrar/dids/DidCreateOptions.kt +++ b/waltid-did/src/commonMain/kotlin/id/walt/did/dids/registrar/dids/DidCreateOptions.kt @@ -6,11 +6,18 @@ import id.walt.did.utils.EnumUtils.enumValueIgnoreCase import kotlinx.serialization.json.JsonElement import kotlinx.serialization.json.jsonObject import kotlinx.serialization.json.jsonPrimitive +import kotlin.js.ExperimentalJsExport +import kotlin.js.JsExport +import kotlin.js.JsName +@ExperimentalJsExport +@JsExport open class DidCreateOptions(val method: String, val options: JsonElement) { + @JsName("secondaryConstructor") constructor(method: String, options: Map) : this(method, options.toJsonElement()) + @JsExport.Ignore inline operator fun get(name: String): T? = options.jsonObject["options"]?.jsonObject?.get(name)?.jsonPrimitive?.content?.let { when (T::class) { @@ -25,6 +32,8 @@ open class DidCreateOptions(val method: String, val options: JsonElement) { } } +@ExperimentalJsExport +@JsExport internal fun options(options: Map, secret: Map = emptyMap()) = mapOf( "options" to options, "didDocument" to mapOf( @@ -35,4 +44,6 @@ internal fun options(options: Map, secret: Map = empty "secret" to secret ) +@ExperimentalJsExport +@JsExport internal fun options(vararg inlineOptions: Pair) = options(mapOf(*inlineOptions)) diff --git a/waltid-did/src/commonMain/kotlin/id/walt/did/dids/registrar/dids/DidEbsiCreateOptions.kt b/waltid-did/src/commonMain/kotlin/id/walt/did/dids/registrar/dids/DidEbsiCreateOptions.kt index 61dfc539c..f979240b9 100644 --- a/waltid-did/src/commonMain/kotlin/id/walt/did/dids/registrar/dids/DidEbsiCreateOptions.kt +++ b/waltid-did/src/commonMain/kotlin/id/walt/did/dids/registrar/dids/DidEbsiCreateOptions.kt @@ -1,5 +1,10 @@ package id.walt.did.dids.registrar.dids +import kotlin.js.ExperimentalJsExport +import kotlin.js.JsExport + +@ExperimentalJsExport +@JsExport class DidEbsiCreateOptions(version: Int, token: String) : DidCreateOptions( method = "ebsi", options = options(options = mapOf("version" to version), secret = mapOf("token" to token)) diff --git a/waltid-did/src/commonMain/kotlin/id/walt/did/dids/registrar/dids/DidEthrCreateOptions.kt b/waltid-did/src/commonMain/kotlin/id/walt/did/dids/registrar/dids/DidEthrCreateOptions.kt index 4b1fbfdeb..6fd4a5ba8 100644 --- a/waltid-did/src/commonMain/kotlin/id/walt/did/dids/registrar/dids/DidEthrCreateOptions.kt +++ b/waltid-did/src/commonMain/kotlin/id/walt/did/dids/registrar/dids/DidEthrCreateOptions.kt @@ -1,5 +1,10 @@ package id.walt.did.dids.registrar.dids +import kotlin.js.ExperimentalJsExport +import kotlin.js.JsExport + +@ExperimentalJsExport +@JsExport class DidEthrCreateOptions(network: String = "goerli") : DidCreateOptions( method = "ethr", options = options("network" to network) diff --git a/waltid-did/src/commonMain/kotlin/id/walt/did/dids/registrar/dids/DidIonCreateOptions.kt b/waltid-did/src/commonMain/kotlin/id/walt/did/dids/registrar/dids/DidIonCreateOptions.kt index 3ac9c8726..96003fe5e 100644 --- a/waltid-did/src/commonMain/kotlin/id/walt/did/dids/registrar/dids/DidIonCreateOptions.kt +++ b/waltid-did/src/commonMain/kotlin/id/walt/did/dids/registrar/dids/DidIonCreateOptions.kt @@ -1,5 +1,10 @@ package id.walt.did.dids.registrar.dids +import kotlin.js.ExperimentalJsExport +import kotlin.js.JsExport + +@ExperimentalJsExport +@JsExport class DidIonCreateOptions : DidCreateOptions( method = "ion", options = emptyMap() diff --git a/waltid-did/src/commonMain/kotlin/id/walt/did/dids/registrar/dids/DidJwkCreateOptions.kt b/waltid-did/src/commonMain/kotlin/id/walt/did/dids/registrar/dids/DidJwkCreateOptions.kt index 2629b1ed3..d0d359a42 100644 --- a/waltid-did/src/commonMain/kotlin/id/walt/did/dids/registrar/dids/DidJwkCreateOptions.kt +++ b/waltid-did/src/commonMain/kotlin/id/walt/did/dids/registrar/dids/DidJwkCreateOptions.kt @@ -1,7 +1,11 @@ package id.walt.did.dids.registrar.dids import id.walt.crypto.keys.KeyType +import kotlin.js.ExperimentalJsExport +import kotlin.js.JsExport +@ExperimentalJsExport +@JsExport class DidJwkCreateOptions(keyType: KeyType = KeyType.Ed25519) : DidCreateOptions( method = "jwk", options = options("keyType" to keyType.name.lowercase()) diff --git a/waltid-did/src/commonMain/kotlin/id/walt/did/dids/registrar/dids/DidKeyCreateOptions.kt b/waltid-did/src/commonMain/kotlin/id/walt/did/dids/registrar/dids/DidKeyCreateOptions.kt index b218810de..30653816e 100644 --- a/waltid-did/src/commonMain/kotlin/id/walt/did/dids/registrar/dids/DidKeyCreateOptions.kt +++ b/waltid-did/src/commonMain/kotlin/id/walt/did/dids/registrar/dids/DidKeyCreateOptions.kt @@ -1,12 +1,16 @@ package id.walt.did.dids.registrar.dids import id.walt.crypto.keys.KeyType +import kotlin.js.ExperimentalJsExport +import kotlin.js.JsExport /** * The did:key implementation defaults to the W3C CCG spec https://w3c-ccg.github.io/did-method-key/. When * _useJwkJcsPub_ is set to `true` the EBSI implementation (jwk_jcs-pub encoding) according * https://hub.ebsi.eu/tools/libraries/key-did-resolver is performed. */ +@ExperimentalJsExport +@JsExport class DidKeyCreateOptions(keyType: KeyType = KeyType.Ed25519, useJwkJcsPub: Boolean = false) : DidCreateOptions( method = "key", options = options("keyType" to keyType.name.lowercase(), "useJwkJcsPub" to useJwkJcsPub) diff --git a/waltid-did/src/commonMain/kotlin/id/walt/did/dids/registrar/dids/DidOydCreateOptions.kt b/waltid-did/src/commonMain/kotlin/id/walt/did/dids/registrar/dids/DidOydCreateOptions.kt index 9ab8fc814..54c759d9b 100644 --- a/waltid-did/src/commonMain/kotlin/id/walt/did/dids/registrar/dids/DidOydCreateOptions.kt +++ b/waltid-did/src/commonMain/kotlin/id/walt/did/dids/registrar/dids/DidOydCreateOptions.kt @@ -1,7 +1,11 @@ package id.walt.did.dids.registrar.dids import kotlinx.serialization.json.JsonObject +import kotlin.js.ExperimentalJsExport +import kotlin.js.JsExport +@ExperimentalJsExport +@JsExport class DidOydCreateOptions(document: JsonObject) : DidCreateOptions( method = "oyd", options = mapOf("didDocument" to document) diff --git a/waltid-did/src/commonMain/kotlin/id/walt/did/dids/registrar/dids/DidSovCreateOptions.kt b/waltid-did/src/commonMain/kotlin/id/walt/did/dids/registrar/dids/DidSovCreateOptions.kt index fc3e4fa9f..6d052ea79 100644 --- a/waltid-did/src/commonMain/kotlin/id/walt/did/dids/registrar/dids/DidSovCreateOptions.kt +++ b/waltid-did/src/commonMain/kotlin/id/walt/did/dids/registrar/dids/DidSovCreateOptions.kt @@ -1,5 +1,10 @@ package id.walt.did.dids.registrar.dids +import kotlin.js.ExperimentalJsExport +import kotlin.js.JsExport + +@ExperimentalJsExport +@JsExport class DidSovCreateOptions(network: String) : DidCreateOptions( method = "sov", options = options("network" to network) diff --git a/waltid-did/src/commonMain/kotlin/id/walt/did/dids/registrar/dids/DidV1CreateOptions.kt b/waltid-did/src/commonMain/kotlin/id/walt/did/dids/registrar/dids/DidV1CreateOptions.kt index f636779bc..5986fe22d 100644 --- a/waltid-did/src/commonMain/kotlin/id/walt/did/dids/registrar/dids/DidV1CreateOptions.kt +++ b/waltid-did/src/commonMain/kotlin/id/walt/did/dids/registrar/dids/DidV1CreateOptions.kt @@ -1,7 +1,11 @@ package id.walt.did.dids.registrar.dids import id.walt.crypto.keys.KeyType +import kotlin.js.ExperimentalJsExport +import kotlin.js.JsExport +@ExperimentalJsExport +@JsExport class DidV1CreateOptions(ledger: String = "test", keyType: KeyType) : DidCreateOptions( method = "v1", options = options("ledger" to ledger, "keytype" to keyType.name.lowercase()) diff --git a/waltid-did/src/commonMain/kotlin/id/walt/did/dids/registrar/dids/DidWebCreateOptions.kt b/waltid-did/src/commonMain/kotlin/id/walt/did/dids/registrar/dids/DidWebCreateOptions.kt index 47ad7560c..a1a1e4049 100644 --- a/waltid-did/src/commonMain/kotlin/id/walt/did/dids/registrar/dids/DidWebCreateOptions.kt +++ b/waltid-did/src/commonMain/kotlin/id/walt/did/dids/registrar/dids/DidWebCreateOptions.kt @@ -1,7 +1,11 @@ package id.walt.did.dids.registrar.dids import id.walt.crypto.keys.KeyType +import kotlin.js.ExperimentalJsExport +import kotlin.js.JsExport +@ExperimentalJsExport +@JsExport class DidWebCreateOptions(domain: String, path: String = "", keyType: KeyType = KeyType.Ed25519) : DidCreateOptions( method = "web", options = options("domain" to domain, "path" to path, "keyType" to keyType) diff --git a/waltid-did/src/commonMain/kotlin/id/walt/did/dids/registrar/local/LocalRegistrarMethod.kt b/waltid-did/src/commonMain/kotlin/id/walt/did/dids/registrar/local/LocalRegistrarMethod.kt index 0b1beafe6..040beb226 100644 --- a/waltid-did/src/commonMain/kotlin/id/walt/did/dids/registrar/local/LocalRegistrarMethod.kt +++ b/waltid-did/src/commonMain/kotlin/id/walt/did/dids/registrar/local/LocalRegistrarMethod.kt @@ -3,10 +3,25 @@ package id.walt.did.dids.registrar.local import id.walt.crypto.keys.Key import id.walt.did.dids.registrar.DidResult import id.walt.did.dids.registrar.dids.DidCreateOptions +import love.forte.plugin.suspendtrans.annotation.JsPromise +import love.forte.plugin.suspendtrans.annotation.JvmAsync +import love.forte.plugin.suspendtrans.annotation.JvmBlocking +import kotlin.js.ExperimentalJsExport +import kotlin.js.JsExport +@ExperimentalJsExport +@JsExport abstract class LocalRegistrarMethod(val method: String) { + @JvmBlocking + @JvmAsync + @JsPromise + @JsExport.Ignore abstract suspend fun register(options: DidCreateOptions): DidResult + @JvmBlocking + @JvmAsync + @JsPromise + @JsExport.Ignore abstract suspend fun registerByKey(key: Key, options: DidCreateOptions): DidResult } diff --git a/waltid-did/src/commonMain/kotlin/id/walt/did/dids/registrar/local/cheqd/DidCheqdRegistrar.kt b/waltid-did/src/commonMain/kotlin/id/walt/did/dids/registrar/local/cheqd/DidCheqdRegistrar.kt index 179f39401..1113d4e91 100644 --- a/waltid-did/src/commonMain/kotlin/id/walt/did/dids/registrar/local/cheqd/DidCheqdRegistrar.kt +++ b/waltid-did/src/commonMain/kotlin/id/walt/did/dids/registrar/local/cheqd/DidCheqdRegistrar.kt @@ -32,7 +32,16 @@ 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 +import love.forte.plugin.suspendtrans.annotation.JsPromise +import love.forte.plugin.suspendtrans.annotation.JvmAsync +import love.forte.plugin.suspendtrans.annotation.JvmBlocking +import kotlin.js.ExperimentalJsExport +import kotlin.js.JsExport + +@ExperimentalJsExport +@JsExport class DidCheqdRegistrar : LocalRegistrarMethod("cheqd") { private val log = KotlinLogging.logger { } @@ -63,9 +72,17 @@ class DidCheqdRegistrar : LocalRegistrarMethod("cheqd") { } } + @JvmBlocking + @JvmAsync + @JsPromise + @JsExport.Ignore override suspend fun register(options: DidCreateOptions): DidResult = registerByKey(LocalKey.generate(KeyType.Ed25519), options) + @JvmBlocking + @JvmAsync + @JsPromise + @JsExport.Ignore override suspend fun registerByKey(key: Key, options: DidCreateOptions): DidResult = createDid(key, options.get("network") ?: "testnet").let { DidResult(it.id, id.walt.did.dids.document.DidDocument(DidCheqdDocument(it, key.exportJWKObject()).toMap())) @@ -139,16 +156,17 @@ class DidCheqdRegistrar : LocalRegistrarMethod("cheqd") { }.body() } + @OptIn(ExperimentalEncodingApi::class) private suspend fun signPayload(key: Key, job: JobActionResponse): List = let { val state = (job.didState as? ActionDidState) ?: error("Unexpected did state") if (!state.action.equals("signPayload", true)) error("Unexpected state action: ${state.action}") val payloads = state.signingRequest.map { - id.walt.did.utils.EncodingUtils.base64Decode(it.serializedPayload) + Base64.decode(it.serializedPayload) } // TODO: sign with key having alias from verification method payloads.map { - id.walt.did.utils.EncodingUtils.base64Encode(key.signRaw(it) as ByteArray) + Base64.encode(key.signRaw(it) as ByteArray) } } -} \ No newline at end of file +} diff --git a/waltid-did/src/commonMain/kotlin/id/walt/did/dids/registrar/local/cheqd/models/job/didstates/DidState.kt b/waltid-did/src/commonMain/kotlin/id/walt/did/dids/registrar/local/cheqd/models/job/didstates/DidState.kt index 93f035ff2..c3851d888 100644 --- a/waltid-did/src/commonMain/kotlin/id/walt/did/dids/registrar/local/cheqd/models/job/didstates/DidState.kt +++ b/waltid-did/src/commonMain/kotlin/id/walt/did/dids/registrar/local/cheqd/models/job/didstates/DidState.kt @@ -10,7 +10,11 @@ import kotlinx.serialization.json.JsonClassDiscriminator import kotlinx.serialization.modules.SerializersModule import kotlinx.serialization.modules.polymorphic import kotlinx.serialization.modules.subclass +import kotlin.js.ExperimentalJsExport +import kotlin.js.JsExport +@ExperimentalJsExport +@JsExport @OptIn(ExperimentalSerializationApi::class) @Polymorphic @Serializable @@ -19,6 +23,8 @@ abstract class DidState { abstract val state: String } +@ExperimentalJsExport +@JsExport val didStateSerializationModule = SerializersModule { polymorphic(DidState::class) { subclass(ActionDidState::class) diff --git a/waltid-did/src/commonMain/kotlin/id/walt/did/dids/registrar/local/cheqd/models/job/didstates/Secret.kt b/waltid-did/src/commonMain/kotlin/id/walt/did/dids/registrar/local/cheqd/models/job/didstates/Secret.kt index 48f0bf41d..41c713bff 100644 --- a/waltid-did/src/commonMain/kotlin/id/walt/did/dids/registrar/local/cheqd/models/job/didstates/Secret.kt +++ b/waltid-did/src/commonMain/kotlin/id/walt/did/dids/registrar/local/cheqd/models/job/didstates/Secret.kt @@ -1,7 +1,11 @@ package id.walt.did.dids.registrar.local.cheqd.models.job.didstates import kotlinx.serialization.Serializable +import kotlin.js.ExperimentalJsExport +import kotlin.js.JsExport +@ExperimentalJsExport +@JsExport @Serializable data class Secret( val signingResponse: List diff --git a/waltid-did/src/commonMain/kotlin/id/walt/did/dids/registrar/local/cheqd/models/job/didstates/SigningResponse.kt b/waltid-did/src/commonMain/kotlin/id/walt/did/dids/registrar/local/cheqd/models/job/didstates/SigningResponse.kt index 2f1439d3e..e038e54d7 100644 --- a/waltid-did/src/commonMain/kotlin/id/walt/did/dids/registrar/local/cheqd/models/job/didstates/SigningResponse.kt +++ b/waltid-did/src/commonMain/kotlin/id/walt/did/dids/registrar/local/cheqd/models/job/didstates/SigningResponse.kt @@ -1,7 +1,11 @@ package id.walt.did.dids.registrar.local.cheqd.models.job.didstates import kotlinx.serialization.Serializable +import kotlin.js.ExperimentalJsExport +import kotlin.js.JsExport +@ExperimentalJsExport +@JsExport @Serializable data class SigningResponse( val signature: String, diff --git a/waltid-did/src/commonMain/kotlin/id/walt/did/dids/registrar/local/cheqd/models/job/didstates/VerificationMethod.kt b/waltid-did/src/commonMain/kotlin/id/walt/did/dids/registrar/local/cheqd/models/job/didstates/VerificationMethod.kt index 4ca97ceff..2688158ea 100644 --- a/waltid-did/src/commonMain/kotlin/id/walt/did/dids/registrar/local/cheqd/models/job/didstates/VerificationMethod.kt +++ b/waltid-did/src/commonMain/kotlin/id/walt/did/dids/registrar/local/cheqd/models/job/didstates/VerificationMethod.kt @@ -2,7 +2,11 @@ package id.walt.did.dids.registrar.local.cheqd.models.job.didstates //import com.beust.klaxon.Json import kotlinx.serialization.Serializable +import kotlin.js.ExperimentalJsExport +import kotlin.js.JsExport +@ExperimentalJsExport +@JsExport @Serializable data class VerificationMethod( val controller: String, diff --git a/waltid-did/src/commonMain/kotlin/id/walt/did/dids/registrar/local/cheqd/models/job/didstates/action/ActionDidState.kt b/waltid-did/src/commonMain/kotlin/id/walt/did/dids/registrar/local/cheqd/models/job/didstates/action/ActionDidState.kt index dc57d6431..d54c1b167 100644 --- a/waltid-did/src/commonMain/kotlin/id/walt/did/dids/registrar/local/cheqd/models/job/didstates/action/ActionDidState.kt +++ b/waltid-did/src/commonMain/kotlin/id/walt/did/dids/registrar/local/cheqd/models/job/didstates/action/ActionDidState.kt @@ -3,7 +3,11 @@ package id.walt.did.dids.registrar.local.cheqd.models.job.didstates.action import id.walt.did.dids.registrar.local.cheqd.models.job.didstates.DidState import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable +import kotlin.js.ExperimentalJsExport +import kotlin.js.JsExport +@ExperimentalJsExport +@JsExport @Serializable @SerialName("action") data class ActionDidState( diff --git a/waltid-did/src/commonMain/kotlin/id/walt/did/dids/registrar/local/cheqd/models/job/didstates/action/Secret.kt b/waltid-did/src/commonMain/kotlin/id/walt/did/dids/registrar/local/cheqd/models/job/didstates/action/Secret.kt index 7c694a94b..a0d12b54a 100644 --- a/waltid-did/src/commonMain/kotlin/id/walt/did/dids/registrar/local/cheqd/models/job/didstates/action/Secret.kt +++ b/waltid-did/src/commonMain/kotlin/id/walt/did/dids/registrar/local/cheqd/models/job/didstates/action/Secret.kt @@ -1,7 +1,11 @@ package id.walt.did.dids.registrar.local.cheqd.models.job.didstates.action import kotlinx.serialization.Serializable +import kotlin.js.ExperimentalJsExport +import kotlin.js.JsExport +@ExperimentalJsExport +@JsExport @Serializable data class Secret( val signingResponse: List diff --git a/waltid-did/src/commonMain/kotlin/id/walt/did/dids/registrar/local/cheqd/models/job/didstates/action/SigningRequest.kt b/waltid-did/src/commonMain/kotlin/id/walt/did/dids/registrar/local/cheqd/models/job/didstates/action/SigningRequest.kt index 277189881..73616731b 100644 --- a/waltid-did/src/commonMain/kotlin/id/walt/did/dids/registrar/local/cheqd/models/job/didstates/action/SigningRequest.kt +++ b/waltid-did/src/commonMain/kotlin/id/walt/did/dids/registrar/local/cheqd/models/job/didstates/action/SigningRequest.kt @@ -1,7 +1,11 @@ package id.walt.did.dids.registrar.local.cheqd.models.job.didstates.action import kotlinx.serialization.Serializable +import kotlin.js.ExperimentalJsExport +import kotlin.js.JsExport +@ExperimentalJsExport +@JsExport @Serializable data class SigningRequest( val alg: String, diff --git a/waltid-did/src/commonMain/kotlin/id/walt/did/dids/registrar/local/cheqd/models/job/didstates/failed/FailedDidState.kt b/waltid-did/src/commonMain/kotlin/id/walt/did/dids/registrar/local/cheqd/models/job/didstates/failed/FailedDidState.kt index f63dbd289..30a0a4899 100644 --- a/waltid-did/src/commonMain/kotlin/id/walt/did/dids/registrar/local/cheqd/models/job/didstates/failed/FailedDidState.kt +++ b/waltid-did/src/commonMain/kotlin/id/walt/did/dids/registrar/local/cheqd/models/job/didstates/failed/FailedDidState.kt @@ -3,7 +3,11 @@ package id.walt.did.dids.registrar.local.cheqd.models.job.didstates.failed import id.walt.did.dids.registrar.local.cheqd.models.job.didstates.DidState import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable +import kotlin.js.ExperimentalJsExport +import kotlin.js.JsExport +@ExperimentalJsExport +@JsExport @Serializable @SerialName("failed") data class FailedDidState( diff --git a/waltid-did/src/commonMain/kotlin/id/walt/did/dids/registrar/local/cheqd/models/job/didstates/finished/DidDocument.kt b/waltid-did/src/commonMain/kotlin/id/walt/did/dids/registrar/local/cheqd/models/job/didstates/finished/DidDocument.kt index 589cd3b40..3c3a25de5 100644 --- a/waltid-did/src/commonMain/kotlin/id/walt/did/dids/registrar/local/cheqd/models/job/didstates/finished/DidDocument.kt +++ b/waltid-did/src/commonMain/kotlin/id/walt/did/dids/registrar/local/cheqd/models/job/didstates/finished/DidDocument.kt @@ -2,7 +2,11 @@ package id.walt.did.dids.registrar.local.cheqd.models.job.didstates.finished import id.walt.did.dids.registrar.local.cheqd.models.job.didstates.VerificationMethod import kotlinx.serialization.Serializable +import kotlin.js.ExperimentalJsExport +import kotlin.js.JsExport +@ExperimentalJsExport +@JsExport @Serializable data class DidDocument( val authentication: List, diff --git a/waltid-did/src/commonMain/kotlin/id/walt/did/dids/registrar/local/cheqd/models/job/didstates/finished/FinishedDidState.kt b/waltid-did/src/commonMain/kotlin/id/walt/did/dids/registrar/local/cheqd/models/job/didstates/finished/FinishedDidState.kt index b37892623..a1d126911 100644 --- a/waltid-did/src/commonMain/kotlin/id/walt/did/dids/registrar/local/cheqd/models/job/didstates/finished/FinishedDidState.kt +++ b/waltid-did/src/commonMain/kotlin/id/walt/did/dids/registrar/local/cheqd/models/job/didstates/finished/FinishedDidState.kt @@ -4,7 +4,11 @@ import id.walt.did.dids.registrar.local.cheqd.models.job.didstates.DidState import id.walt.did.dids.registrar.local.cheqd.models.job.didstates.Secret import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable +import kotlin.js.ExperimentalJsExport +import kotlin.js.JsExport +@ExperimentalJsExport +@JsExport @Serializable @SerialName("finished") data class FinishedDidState( diff --git a/waltid-did/src/commonMain/kotlin/id/walt/did/dids/registrar/local/cheqd/models/job/request/JobCreateRequest.kt b/waltid-did/src/commonMain/kotlin/id/walt/did/dids/registrar/local/cheqd/models/job/request/JobCreateRequest.kt index 3be48e459..23979c493 100644 --- a/waltid-did/src/commonMain/kotlin/id/walt/did/dids/registrar/local/cheqd/models/job/request/JobCreateRequest.kt +++ b/waltid-did/src/commonMain/kotlin/id/walt/did/dids/registrar/local/cheqd/models/job/request/JobCreateRequest.kt @@ -2,7 +2,11 @@ package id.walt.did.dids.registrar.local.cheqd.models.job.request import id.walt.did.dids.registrar.local.cheqd.models.job.response.didresponse.DidDocObject import kotlinx.serialization.Serializable +import kotlin.js.ExperimentalJsExport +import kotlin.js.JsExport +@ExperimentalJsExport +@JsExport @Serializable data class JobCreateRequest( val didDocument: DidDocObject diff --git a/waltid-did/src/commonMain/kotlin/id/walt/did/dids/registrar/local/cheqd/models/job/request/JobDeactivateRequest.kt b/waltid-did/src/commonMain/kotlin/id/walt/did/dids/registrar/local/cheqd/models/job/request/JobDeactivateRequest.kt index be5185626..b9ef9ae2c 100644 --- a/waltid-did/src/commonMain/kotlin/id/walt/did/dids/registrar/local/cheqd/models/job/request/JobDeactivateRequest.kt +++ b/waltid-did/src/commonMain/kotlin/id/walt/did/dids/registrar/local/cheqd/models/job/request/JobDeactivateRequest.kt @@ -1,7 +1,11 @@ package id.walt.did.dids.registrar.local.cheqd.models.job.request import kotlinx.serialization.Serializable +import kotlin.js.ExperimentalJsExport +import kotlin.js.JsExport +@ExperimentalJsExport +@JsExport @Serializable data class JobDeactivateRequest( val did: String diff --git a/waltid-did/src/commonMain/kotlin/id/walt/did/dids/registrar/local/cheqd/models/job/request/JobSignRequest.kt b/waltid-did/src/commonMain/kotlin/id/walt/did/dids/registrar/local/cheqd/models/job/request/JobSignRequest.kt index 0dc1767ec..135406f4c 100644 --- a/waltid-did/src/commonMain/kotlin/id/walt/did/dids/registrar/local/cheqd/models/job/request/JobSignRequest.kt +++ b/waltid-did/src/commonMain/kotlin/id/walt/did/dids/registrar/local/cheqd/models/job/request/JobSignRequest.kt @@ -2,7 +2,11 @@ package id.walt.did.dids.registrar.local.cheqd.models.job.request import id.walt.did.dids.registrar.local.cheqd.models.job.didstates.Secret import kotlinx.serialization.Serializable +import kotlin.js.ExperimentalJsExport +import kotlin.js.JsExport +@ExperimentalJsExport +@JsExport @Serializable data class JobSignRequest( val jobId: String, diff --git a/waltid-did/src/commonMain/kotlin/id/walt/did/dids/registrar/local/cheqd/models/job/response/JobActionResponse.kt b/waltid-did/src/commonMain/kotlin/id/walt/did/dids/registrar/local/cheqd/models/job/response/JobActionResponse.kt index b82a8ce75..ca0bf73db 100644 --- a/waltid-did/src/commonMain/kotlin/id/walt/did/dids/registrar/local/cheqd/models/job/response/JobActionResponse.kt +++ b/waltid-did/src/commonMain/kotlin/id/walt/did/dids/registrar/local/cheqd/models/job/response/JobActionResponse.kt @@ -2,7 +2,11 @@ package id.walt.did.dids.registrar.local.cheqd.models.job.response import id.walt.did.dids.registrar.local.cheqd.models.job.didstates.DidState import kotlinx.serialization.Serializable +import kotlin.js.ExperimentalJsExport +import kotlin.js.JsExport +@ExperimentalJsExport +@JsExport @Serializable data class JobActionResponse( val didState: DidState, diff --git a/waltid-did/src/commonMain/kotlin/id/walt/did/dids/registrar/local/cheqd/models/job/response/didresponse/CheqdKey.kt b/waltid-did/src/commonMain/kotlin/id/walt/did/dids/registrar/local/cheqd/models/job/response/didresponse/CheqdKey.kt index 78c285944..f61afc87b 100644 --- a/waltid-did/src/commonMain/kotlin/id/walt/did/dids/registrar/local/cheqd/models/job/response/didresponse/CheqdKey.kt +++ b/waltid-did/src/commonMain/kotlin/id/walt/did/dids/registrar/local/cheqd/models/job/response/didresponse/CheqdKey.kt @@ -1,7 +1,11 @@ package id.walt.did.dids.registrar.local.cheqd.models.job.response.didresponse import kotlinx.serialization.Serializable +import kotlin.js.ExperimentalJsExport +import kotlin.js.JsExport +@ExperimentalJsExport +@JsExport @Serializable data class CheqdKey( val publicKeyHex: String, diff --git a/waltid-did/src/commonMain/kotlin/id/walt/did/dids/registrar/local/cheqd/models/job/response/didresponse/DidDocObject.kt b/waltid-did/src/commonMain/kotlin/id/walt/did/dids/registrar/local/cheqd/models/job/response/didresponse/DidDocObject.kt index 7067f652b..9e4624128 100644 --- a/waltid-did/src/commonMain/kotlin/id/walt/did/dids/registrar/local/cheqd/models/job/response/didresponse/DidDocObject.kt +++ b/waltid-did/src/commonMain/kotlin/id/walt/did/dids/registrar/local/cheqd/models/job/response/didresponse/DidDocObject.kt @@ -2,7 +2,11 @@ package id.walt.did.dids.registrar.local.cheqd.models.job.response.didresponse import id.walt.did.dids.registrar.local.cheqd.models.job.didstates.VerificationMethod import kotlinx.serialization.Serializable +import kotlin.js.ExperimentalJsExport +import kotlin.js.JsExport +@ExperimentalJsExport +@JsExport @Serializable data class DidDocObject( val authentication: List, diff --git a/waltid-did/src/commonMain/kotlin/id/walt/did/dids/registrar/local/cheqd/models/job/response/didresponse/DidGetResponse.kt b/waltid-did/src/commonMain/kotlin/id/walt/did/dids/registrar/local/cheqd/models/job/response/didresponse/DidGetResponse.kt index dd32c3250..376c7612b 100644 --- a/waltid-did/src/commonMain/kotlin/id/walt/did/dids/registrar/local/cheqd/models/job/response/didresponse/DidGetResponse.kt +++ b/waltid-did/src/commonMain/kotlin/id/walt/did/dids/registrar/local/cheqd/models/job/response/didresponse/DidGetResponse.kt @@ -1,7 +1,11 @@ package id.walt.did.dids.registrar.local.cheqd.models.job.response.didresponse import kotlinx.serialization.Serializable +import kotlin.js.ExperimentalJsExport +import kotlin.js.JsExport +@ExperimentalJsExport +@JsExport @Serializable data class DidGetResponse( val didDoc: DidDocObject, diff --git a/waltid-did/src/commonMain/kotlin/id/walt/did/dids/registrar/local/jwk/DidJwkRegistrar.kt b/waltid-did/src/commonMain/kotlin/id/walt/did/dids/registrar/local/jwk/DidJwkRegistrar.kt index 9af9ac489..a6dc77b0d 100644 --- a/waltid-did/src/commonMain/kotlin/id/walt/did/dids/registrar/local/jwk/DidJwkRegistrar.kt +++ b/waltid-did/src/commonMain/kotlin/id/walt/did/dids/registrar/local/jwk/DidJwkRegistrar.kt @@ -10,12 +10,27 @@ import id.walt.did.dids.registrar.DidResult import id.walt.did.dids.registrar.dids.DidCreateOptions import id.walt.did.dids.registrar.local.LocalRegistrarMethod import io.ktor.utils.io.core.* +import love.forte.plugin.suspendtrans.annotation.JsPromise +import love.forte.plugin.suspendtrans.annotation.JvmAsync +import love.forte.plugin.suspendtrans.annotation.JvmBlocking +import kotlin.js.ExperimentalJsExport +import kotlin.js.JsExport +@ExperimentalJsExport +@JsExport class DidJwkRegistrar : LocalRegistrarMethod("jwk") { + @JvmBlocking + @JvmAsync + @JsPromise + @JsExport.Ignore override suspend fun register(options: DidCreateOptions) = options.get("keyType")?.let { registerByKey(LocalKey.generate(it), options) } ?: throw IllegalArgumentException("KeyType option not found.") + @JvmBlocking + @JvmAsync + @JsPromise + @JsExport.Ignore override suspend fun registerByKey(key: Key, options: DidCreateOptions): DidResult { val did = "did:jwk:${key.getPublicKey().exportJWK().toByteArray().encodeToBase64Url()}" diff --git a/waltid-did/src/commonMain/kotlin/id/walt/did/dids/registrar/local/key/DidKeyRegistrar.kt b/waltid-did/src/commonMain/kotlin/id/walt/did/dids/registrar/local/key/DidKeyRegistrar.kt index 135e362ae..26a90a918 100644 --- a/waltid-did/src/commonMain/kotlin/id/walt/did/dids/registrar/local/key/DidKeyRegistrar.kt +++ b/waltid-did/src/commonMain/kotlin/id/walt/did/dids/registrar/local/key/DidKeyRegistrar.kt @@ -14,13 +14,28 @@ import id.walt.did.dids.registrar.dids.DidCreateOptions import id.walt.did.dids.registrar.local.LocalRegistrarMethod import id.walt.did.utils.JsonCanonicalization import kotlinx.serialization.json.JsonObject +import love.forte.plugin.suspendtrans.annotation.JsPromise +import love.forte.plugin.suspendtrans.annotation.JvmAsync +import love.forte.plugin.suspendtrans.annotation.JvmBlocking +import kotlin.js.ExperimentalJsExport +import kotlin.js.JsExport +@ExperimentalJsExport +@JsExport class DidKeyRegistrar : LocalRegistrarMethod("key") { + @JvmBlocking + @JvmAsync + @JsPromise + @JsExport.Ignore override suspend fun register(options: DidCreateOptions): DidResult = options.get("keyType")?.let { registerByKey(LocalKey.generate(it), options) } ?: throw IllegalArgumentException("KeyType option not found.") + @JvmBlocking + @JvmAsync + @JsPromise + @JsExport.Ignore override suspend fun registerByKey(key: Key, options: DidCreateOptions): DidResult = options.let { if (key.keyType !in setOf( KeyType.Ed25519, diff --git a/waltid-did/src/commonMain/kotlin/id/walt/did/dids/registrar/local/key/IdentifierComponents.kt b/waltid-did/src/commonMain/kotlin/id/walt/did/dids/registrar/local/key/IdentifierComponents.kt index e1ce84f47..33e9f76c1 100644 --- a/waltid-did/src/commonMain/kotlin/id/walt/did/dids/registrar/local/key/IdentifierComponents.kt +++ b/waltid-did/src/commonMain/kotlin/id/walt/did/dids/registrar/local/key/IdentifierComponents.kt @@ -1,5 +1,10 @@ package id.walt.did.dids.registrar.local.key +import kotlin.js.ExperimentalJsExport +import kotlin.js.JsExport + +@ExperimentalJsExport +@JsExport internal data class IdentifierComponents( val multiCodecKeyCode: UInt, val pubKeyBytes: ByteArray, diff --git a/waltid-did/src/commonMain/kotlin/id/walt/did/dids/registrar/local/web/DidWebRegistrar.kt b/waltid-did/src/commonMain/kotlin/id/walt/did/dids/registrar/local/web/DidWebRegistrar.kt index fc9633b4c..3ff5550e0 100644 --- a/waltid-did/src/commonMain/kotlin/id/walt/did/dids/registrar/local/web/DidWebRegistrar.kt +++ b/waltid-did/src/commonMain/kotlin/id/walt/did/dids/registrar/local/web/DidWebRegistrar.kt @@ -8,21 +8,36 @@ import id.walt.did.dids.document.DidWebDocument import id.walt.did.dids.registrar.DidResult import id.walt.did.dids.registrar.dids.DidCreateOptions import id.walt.did.dids.registrar.local.LocalRegistrarMethod -import id.walt.did.utils.EncodingUtils.urlEncode import id.walt.did.utils.ExtensionMethods.ensurePrefix +import love.forte.plugin.suspendtrans.annotation.JsPromise +import love.forte.plugin.suspendtrans.annotation.JvmAsync +import love.forte.plugin.suspendtrans.annotation.JvmBlocking +import kotlin.js.ExperimentalJsExport +import kotlin.js.JsExport +import net.thauvin.erik.urlencoder.UrlEncoderUtil +@ExperimentalJsExport +@JsExport class DidWebRegistrar : LocalRegistrarMethod("web") { + @JvmBlocking + @JvmAsync + @JsPromise + @JsExport.Ignore override suspend fun register(options: DidCreateOptions): DidResult = options.get("keyType")?.let { registerByKey(LocalKey.generate(it), options) } ?: throw IllegalArgumentException("keyType option not found.") + @JvmBlocking + @JvmAsync + @JsPromise + @JsExport.Ignore override suspend fun registerByKey(key: Key, options: DidCreateOptions): DidResult = options.get("domain")?.takeIf { it.isNotEmpty() }?.let { - val domain = urlEncode(it) + val domain = UrlEncoderUtil.encode(it) val path = options.get("path")?.takeIf { it.isNotEmpty() }?.let { - it.ensurePrefix("/").split("/").joinToString(":") { part -> urlEncode(part) } + it.ensurePrefix("/").split("/").joinToString(":") { part -> UrlEncoderUtil.encode(part) } } ?: "" DidResult( "did:web:$domain$path", DidDocument( diff --git a/waltid-did/src/commonMain/kotlin/id/walt/did/dids/resolver/DidResolver.kt b/waltid-did/src/commonMain/kotlin/id/walt/did/dids/resolver/DidResolver.kt index 3471a1ba2..98140f96a 100644 --- a/waltid-did/src/commonMain/kotlin/id/walt/did/dids/resolver/DidResolver.kt +++ b/waltid-did/src/commonMain/kotlin/id/walt/did/dids/resolver/DidResolver.kt @@ -2,12 +2,31 @@ package id.walt.did.dids.resolver import id.walt.crypto.keys.Key import kotlinx.serialization.json.JsonObject +import love.forte.plugin.suspendtrans.annotation.JsPromise +import love.forte.plugin.suspendtrans.annotation.JvmAsync +import love.forte.plugin.suspendtrans.annotation.JvmBlocking +import kotlin.js.ExperimentalJsExport +import kotlin.js.JsExport +@ExperimentalJsExport +@JsExport interface DidResolver { val name: String + @JvmBlocking + @JvmAsync + @JsPromise + @JsExport.Ignore suspend fun getSupportedMethods(): Result> + @JvmBlocking + @JvmAsync + @JsPromise + @JsExport.Ignore suspend fun resolve(did: String): Result + @JvmBlocking + @JvmAsync + @JsPromise + @JsExport.Ignore suspend fun resolveToKey(did: String): Result } diff --git a/waltid-did/src/commonMain/kotlin/id/walt/did/dids/resolver/DidResolverRegistrations.kt b/waltid-did/src/commonMain/kotlin/id/walt/did/dids/resolver/DidResolverRegistrations.kt index e3504f29b..36822b45c 100644 --- a/waltid-did/src/commonMain/kotlin/id/walt/did/dids/resolver/DidResolverRegistrations.kt +++ b/waltid-did/src/commonMain/kotlin/id/walt/did/dids/resolver/DidResolverRegistrations.kt @@ -1,5 +1,10 @@ package id.walt.did.dids.resolver +import kotlin.js.ExperimentalJsExport +import kotlin.js.JsExport + +@ExperimentalJsExport +@JsExport object DidResolverRegistrations { val didResolvers = setOf( diff --git a/waltid-did/src/commonMain/kotlin/id/walt/did/dids/resolver/LocalResolver.kt b/waltid-did/src/commonMain/kotlin/id/walt/did/dids/resolver/LocalResolver.kt index 4c477f90b..b9e1e5cd8 100644 --- a/waltid-did/src/commonMain/kotlin/id/walt/did/dids/resolver/LocalResolver.kt +++ b/waltid-did/src/commonMain/kotlin/id/walt/did/dids/resolver/LocalResolver.kt @@ -10,7 +10,14 @@ import io.ktor.client.* import io.ktor.client.plugins.contentnegotiation.* import io.ktor.serialization.kotlinx.json.* import kotlinx.serialization.json.JsonObject - +import love.forte.plugin.suspendtrans.annotation.JsPromise +import love.forte.plugin.suspendtrans.annotation.JvmAsync +import love.forte.plugin.suspendtrans.annotation.JvmBlocking +import kotlin.js.ExperimentalJsExport +import kotlin.js.JsExport + +@ExperimentalJsExport +@JsExport class LocalResolver : DidResolver { override val name = "walt.id local resolver" private val http = HttpClient() { @@ -29,6 +36,10 @@ class LocalResolver : DidResolver { resolvers.remove(method) } + @JvmBlocking + @JvmAsync + @JsPromise + @JsExport.Ignore override suspend fun getSupportedMethods(): Result> = Result.success(resolvers.keys) private fun getResolverForDid(did: String): LocalResolverMethod { @@ -36,9 +47,17 @@ class LocalResolver : DidResolver { return resolvers[method] ?: throw IllegalArgumentException("No resolver for method: $did") } + @JvmBlocking + @JvmAsync + @JsPromise + @JsExport.Ignore override suspend fun resolve(did: String): Result = getResolverForDid(did).resolve(did).map { it.toJsonObject() } + @JvmBlocking + @JvmAsync + @JsPromise + @JsExport.Ignore override suspend fun resolveToKey(did: String): Result = getResolverForDid(did).resolveToKey(did) } diff --git a/waltid-did/src/commonMain/kotlin/id/walt/did/dids/resolver/UniresolverResolver.kt b/waltid-did/src/commonMain/kotlin/id/walt/did/dids/resolver/UniresolverResolver.kt index 8eb70418f..0ab168bf1 100644 --- a/waltid-did/src/commonMain/kotlin/id/walt/did/dids/resolver/UniresolverResolver.kt +++ b/waltid-did/src/commonMain/kotlin/id/walt/did/dids/resolver/UniresolverResolver.kt @@ -13,7 +13,14 @@ import io.ktor.serialization.kotlinx.json.* import kotlinx.serialization.json.JsonArray import kotlinx.serialization.json.JsonObject import kotlinx.serialization.json.jsonPrimitive +import love.forte.plugin.suspendtrans.annotation.JsPromise +import love.forte.plugin.suspendtrans.annotation.JvmAsync +import love.forte.plugin.suspendtrans.annotation.JvmBlocking +import kotlin.js.ExperimentalJsExport +import kotlin.js.JsExport +@ExperimentalJsExport +@JsExport class UniresolverResolver : DidResolver { @Suppress("MemberVisibilityCanBePrivate") //var resolverUrl = "http://localhost:8080/1.0" @@ -22,6 +29,10 @@ class UniresolverResolver : DidResolver { override val name = "uniresolver @ $resolverUrl" + @JvmBlocking + @JvmAsync + @JsPromise + @JsExport.Ignore override suspend fun getSupportedMethods() = runCatching { lazyOf(getMethods()).value } private val http = HttpClient { @@ -33,9 +44,17 @@ class UniresolverResolver : DidResolver { } } + @JvmBlocking + @JvmAsync + @JsPromise + @JsExport.Ignore override suspend fun resolve(did: String): Result = runCatching { http.get("$resolverUrl/identifiers/$did").body() } + @JvmBlocking + @JvmAsync + @JsPromise + @JsExport.Ignore override suspend fun resolveToKey(did: String): Result = resolve(did).fold( onSuccess = { VerificationMaterial.get(it)?.let { diff --git a/waltid-did/src/commonMain/kotlin/id/walt/did/dids/resolver/local/DidCheqdResolver.kt b/waltid-did/src/commonMain/kotlin/id/walt/did/dids/resolver/local/DidCheqdResolver.kt index d540b4547..8e030ba66 100644 --- a/waltid-did/src/commonMain/kotlin/id/walt/did/dids/resolver/local/DidCheqdResolver.kt +++ b/waltid-did/src/commonMain/kotlin/id/walt/did/dids/resolver/local/DidCheqdResolver.kt @@ -10,14 +10,29 @@ import kotlinx.serialization.json.Json import kotlinx.serialization.json.JsonObject import kotlinx.serialization.json.jsonObject import kotlinx.serialization.json.jsonPrimitive +import love.forte.plugin.suspendtrans.annotation.JsPromise +import love.forte.plugin.suspendtrans.annotation.JvmAsync +import love.forte.plugin.suspendtrans.annotation.JvmBlocking +import kotlin.js.ExperimentalJsExport +import kotlin.js.JsExport +@ExperimentalJsExport +@JsExport class DidCheqdResolver : LocalResolverMethod("cheqd") { private val httpClient = HttpClient() //TODO: inject + @JvmBlocking + @JvmAsync + @JsPromise + @JsExport.Ignore override suspend fun resolve(did: String): Result = runCatching { resolveDid(did) } + @JvmBlocking + @JvmAsync + @JsPromise + @JsExport.Ignore override suspend fun resolveToKey(did: String): Result { TODO("Not yet implemented") // response verificationMethod contains only publicKeyMultibase diff --git a/waltid-did/src/commonMain/kotlin/id/walt/did/dids/resolver/local/DidJwkResolver.kt b/waltid-did/src/commonMain/kotlin/id/walt/did/dids/resolver/local/DidJwkResolver.kt index e8118767f..159532c64 100644 --- a/waltid-did/src/commonMain/kotlin/id/walt/did/dids/resolver/local/DidJwkResolver.kt +++ b/waltid-did/src/commonMain/kotlin/id/walt/did/dids/resolver/local/DidJwkResolver.kt @@ -6,8 +6,19 @@ import id.walt.crypto.utils.Base64Utils.base64UrlDecode import id.walt.did.dids.DidUtils import id.walt.did.dids.document.DidDocument import id.walt.did.dids.document.DidJwkDocument +import love.forte.plugin.suspendtrans.annotation.JsPromise +import love.forte.plugin.suspendtrans.annotation.JvmAsync +import love.forte.plugin.suspendtrans.annotation.JvmBlocking +import kotlin.js.ExperimentalJsExport +import kotlin.js.JsExport +@ExperimentalJsExport +@JsExport class DidJwkResolver : LocalResolverMethod("jwk") { + @JvmBlocking + @JvmAsync + @JsPromise + @JsExport.Ignore override suspend fun resolve(did: String): Result { val keyResult = resolveToKey(did) if (keyResult.isFailure) return Result.failure(keyResult.exceptionOrNull()!!) @@ -19,6 +30,10 @@ class DidJwkResolver : LocalResolverMethod("jwk") { return Result.success(didDocument) } + @JvmBlocking + @JvmAsync + @JsPromise + @JsExport.Ignore override suspend fun resolveToKey(did: String): Result = LocalKey.importJWK(DidUtils.pathFromDid(did)!!.base64UrlDecode().decodeToString()) } diff --git a/waltid-did/src/commonMain/kotlin/id/walt/did/dids/resolver/local/DidKeyResolver.kt b/waltid-did/src/commonMain/kotlin/id/walt/did/dids/resolver/local/DidKeyResolver.kt index 5906dade7..a6604eaf3 100644 --- a/waltid-did/src/commonMain/kotlin/id/walt/did/dids/resolver/local/DidKeyResolver.kt +++ b/waltid-did/src/commonMain/kotlin/id/walt/did/dids/resolver/local/DidKeyResolver.kt @@ -5,8 +5,19 @@ import id.walt.did.dids.DidUtils import id.walt.did.dids.document.DidDocument import id.walt.did.dids.document.DidKeyDocument import id.walt.did.utils.KeyUtils +import love.forte.plugin.suspendtrans.annotation.JsPromise +import love.forte.plugin.suspendtrans.annotation.JvmAsync +import love.forte.plugin.suspendtrans.annotation.JvmBlocking +import kotlin.js.ExperimentalJsExport +import kotlin.js.JsExport +@ExperimentalJsExport +@JsExport class DidKeyResolver : LocalResolverMethod("key") { + @JvmBlocking + @JvmAsync + @JsPromise + @JsExport.Ignore override suspend fun resolve(did: String): Result = resolveToKey(did).fold( onSuccess = { Result.success( @@ -21,6 +32,10 @@ class DidKeyResolver : LocalResolverMethod("key") { } ) + @JvmBlocking + @JvmAsync + @JsPromise + @JsExport.Ignore override suspend fun resolveToKey(did: String): Result = DidUtils.identifierFromDid(did)?.let { KeyUtils.fromPublicKeyMultiBase(it) } ?: Result.failure(Throwable("Failed to extract identifier from: $did")) diff --git a/waltid-did/src/commonMain/kotlin/id/walt/did/dids/resolver/local/DidWebResolver.kt b/waltid-did/src/commonMain/kotlin/id/walt/did/dids/resolver/local/DidWebResolver.kt index 818e76d45..b5a2d44a5 100644 --- a/waltid-did/src/commonMain/kotlin/id/walt/did/dids/resolver/local/DidWebResolver.kt +++ b/waltid-did/src/commonMain/kotlin/id/walt/did/dids/resolver/local/DidWebResolver.kt @@ -12,9 +12,20 @@ import kotlinx.serialization.json.Json import kotlinx.serialization.json.JsonObject import kotlinx.serialization.json.jsonArray import kotlinx.serialization.json.jsonObject +import love.forte.plugin.suspendtrans.annotation.JsPromise +import love.forte.plugin.suspendtrans.annotation.JvmAsync +import love.forte.plugin.suspendtrans.annotation.JvmBlocking +import kotlin.js.ExperimentalJsExport +import kotlin.js.JsExport +@ExperimentalJsExport +@JsExport class DidWebResolver(private val client: HttpClient) : LocalResolverMethod("web") { + @JvmBlocking + @JvmAsync + @JsPromise + @JsExport.Ignore override suspend fun resolve(did: String): Result { val url = resolveDidToUrl(did) @@ -27,6 +38,10 @@ class DidWebResolver(private val client: HttpClient) : LocalResolverMethod("web" return response } + @JvmBlocking + @JvmAsync + @JsPromise + @JsExport.Ignore override suspend fun resolveToKey(did: String): Result { val didDocumentResult = resolve(did) if (didDocumentResult.isFailure) return Result.failure(didDocumentResult.exceptionOrNull()!!) @@ -58,6 +73,10 @@ class DidWebResolver(private val client: HttpClient) : LocalResolverMethod("web" "$URL_PROTOCOL://$domain$path" } ?: throw IllegalArgumentException("Unexpected did format (missing identifier): $did") + @JvmBlocking + @JvmAsync + @JsPromise + @JsExport.Ignore suspend fun tryConvertAnyPublicKeyJwkToKey(publicKeyJwks: List): Result { publicKeyJwks.forEach { publicKeyJwk -> val result = LocalKey.importJWK(publicKeyJwk) diff --git a/waltid-did/src/commonMain/kotlin/id/walt/did/dids/resolver/local/LocalResolverMethod.kt b/waltid-did/src/commonMain/kotlin/id/walt/did/dids/resolver/local/LocalResolverMethod.kt index 7c9641c90..098003740 100644 --- a/waltid-did/src/commonMain/kotlin/id/walt/did/dids/resolver/local/LocalResolverMethod.kt +++ b/waltid-did/src/commonMain/kotlin/id/walt/did/dids/resolver/local/LocalResolverMethod.kt @@ -1,10 +1,25 @@ package id.walt.did.dids.resolver.local import id.walt.did.dids.document.DidDocument +import love.forte.plugin.suspendtrans.annotation.JsPromise +import love.forte.plugin.suspendtrans.annotation.JvmAsync +import love.forte.plugin.suspendtrans.annotation.JvmBlocking +import kotlin.js.ExperimentalJsExport +import kotlin.js.JsExport +@ExperimentalJsExport +@JsExport abstract class LocalResolverMethod(val method: String) { + @JvmBlocking + @JvmAsync + @JsPromise + @JsExport.Ignore abstract suspend fun resolve(did: String): Result + @JvmBlocking + @JvmAsync + @JsPromise + @JsExport.Ignore abstract suspend fun resolveToKey(did: String): Result } diff --git a/waltid-did/src/commonMain/kotlin/id/walt/did/helpers/WaltidServices.kt b/waltid-did/src/commonMain/kotlin/id/walt/did/helpers/WaltidServices.kt index bb5b4e04c..53fe14b44 100644 --- a/waltid-did/src/commonMain/kotlin/id/walt/did/helpers/WaltidServices.kt +++ b/waltid-did/src/commonMain/kotlin/id/walt/did/helpers/WaltidServices.kt @@ -1,13 +1,28 @@ package id.walt.did.helpers import id.walt.did.dids.DidService +import love.forte.plugin.suspendtrans.annotation.JsPromise +import love.forte.plugin.suspendtrans.annotation.JvmAsync +import love.forte.plugin.suspendtrans.annotation.JvmBlocking +import kotlin.js.ExperimentalJsExport +import kotlin.js.JsExport +@ExperimentalJsExport +@JsExport object WaltidServices { + @JvmBlocking + @JvmAsync + @JsPromise + @JsExport.Ignore suspend fun init() { DidService.init() } + @JvmBlocking + @JvmAsync + @JsPromise + @JsExport.Ignore suspend fun minimalInit() { DidService.minimalInit() } diff --git a/waltid-did/src/commonMain/kotlin/id/walt/did/utils/EncodingUtils.kt b/waltid-did/src/commonMain/kotlin/id/walt/did/utils/EncodingUtils.kt deleted file mode 100644 index c0fa4b98b..000000000 --- a/waltid-did/src/commonMain/kotlin/id/walt/did/utils/EncodingUtils.kt +++ /dev/null @@ -1,12 +0,0 @@ -package id.walt.did.utils - -expect object EncodingUtils { - fun urlEncode(path: String): String - fun urlDecode(path: String): String - fun base64Encode(data: ByteArray): String - fun base64Decode(data: String): ByteArray - fun base58Encode(byteArray: ByteArray): String - fun base58Decode(base58String: String): ByteArray - fun fromHexString(hexString: String): ByteArray - fun toHexString(byteArray: ByteArray): String -} diff --git a/waltid-did/src/commonMain/kotlin/id/walt/did/utils/EnumUtils.kt b/waltid-did/src/commonMain/kotlin/id/walt/did/utils/EnumUtils.kt index 85135b853..3f18c1515 100644 --- a/waltid-did/src/commonMain/kotlin/id/walt/did/utils/EnumUtils.kt +++ b/waltid-did/src/commonMain/kotlin/id/walt/did/utils/EnumUtils.kt @@ -1,11 +1,17 @@ package id.walt.did.utils +import kotlin.js.ExperimentalJsExport +import kotlin.js.JsExport + +@ExperimentalJsExport +@JsExport object EnumUtils { /** * Gets the enum value by its name * @param [value] enum value * @return The enum value if found, otherwise - null */ + @JsExport.Ignore inline fun > enumValueIgnoreCase(value: String): T? = enumValues().firstOrNull { it.name.equals(value, true) } diff --git a/waltid-did/src/commonMain/kotlin/id/walt/did/utils/ExtensionMethods.kt b/waltid-did/src/commonMain/kotlin/id/walt/did/utils/ExtensionMethods.kt index 29cf6d0b5..f08f99d81 100644 --- a/waltid-did/src/commonMain/kotlin/id/walt/did/utils/ExtensionMethods.kt +++ b/waltid-did/src/commonMain/kotlin/id/walt/did/utils/ExtensionMethods.kt @@ -1,5 +1,10 @@ package id.walt.did.utils +import kotlin.js.ExperimentalJsExport +import kotlin.js.JsExport + +@ExperimentalJsExport +@JsExport object ExtensionMethods { fun String.ensurePrefix(prefix: String) = this.takeIf { it.startsWith(prefix) } ?: prefix.plus(this) } diff --git a/waltid-did/src/commonMain/kotlin/id/walt/did/utils/KeyMaterial.kt b/waltid-did/src/commonMain/kotlin/id/walt/did/utils/KeyMaterial.kt index 468b168aa..303b5d45b 100644 --- a/waltid-did/src/commonMain/kotlin/id/walt/did/utils/KeyMaterial.kt +++ b/waltid-did/src/commonMain/kotlin/id/walt/did/utils/KeyMaterial.kt @@ -4,14 +4,26 @@ import id.walt.crypto.keys.Key import id.walt.crypto.keys.KeyType import id.walt.crypto.keys.LocalKey import id.walt.crypto.keys.LocalKeyMetadata +import id.walt.crypto.utils.decodeBase58 import id.walt.did.utils.KeyUtils.fromPublicKeyMultiBase import id.walt.did.utils.KeyUtils.getKeyTypeForVerificationMaterialType import kotlinx.serialization.json.JsonElement import kotlinx.serialization.json.JsonObject import kotlinx.serialization.json.jsonObject import kotlinx.serialization.json.jsonPrimitive +import love.forte.plugin.suspendtrans.annotation.JsPromise +import love.forte.plugin.suspendtrans.annotation.JvmAsync +import love.forte.plugin.suspendtrans.annotation.JvmBlocking +import kotlin.js.ExperimentalJsExport +import kotlin.js.JsExport +@ExperimentalJsExport +@JsExport object KeyMaterial { + @JvmBlocking + @JvmAsync + @JsPromise + @JsExport.Ignore suspend fun get(element: JsonElement): Result = when (element) { is JsonObject -> importKey(element) // is JsonPrimitive -> TODO: did @@ -39,12 +51,18 @@ object KeyMaterial { private suspend fun importJwk(element: JsonObject): Result = LocalKey.importJWK(element.toString()) private suspend fun importBase58(content: String, type: KeyType): Result = runCatching { - LocalKey.importRawPublicKey(type, EncodingUtils.base58Decode(content), LocalKeyMetadata()) + LocalKey.importRawPublicKey(type, content.decodeBase58(), LocalKeyMetadata()) } private suspend fun importMultibase(content: String): Result = fromPublicKeyMultiBase(content) private suspend fun importHex(content: String, type: KeyType): Result = runCatching { - LocalKey.importRawPublicKey(type, EncodingUtils.fromHexString(content), LocalKeyMetadata()) + LocalKey.importRawPublicKey(type, fromHexString(content), LocalKeyMetadata()) } -} \ No newline at end of file + + private fun fromHexString(hexString: String) = + hexString.replace(" ", "").chunked(2).map { it.toInt(16).toByte() }.toByteArray() + + /*private fun toHexString(byteArray: ByteArray) = + byteArray.joinToString("") { String.format("%02X ", (it.toInt() and 0xFF)) }*/ +} diff --git a/waltid-did/src/commonMain/kotlin/id/walt/did/utils/KeyUtils.kt b/waltid-did/src/commonMain/kotlin/id/walt/did/utils/KeyUtils.kt index 8f4a22355..e3890842e 100644 --- a/waltid-did/src/commonMain/kotlin/id/walt/did/utils/KeyUtils.kt +++ b/waltid-did/src/commonMain/kotlin/id/walt/did/utils/KeyUtils.kt @@ -6,8 +6,19 @@ import id.walt.crypto.keys.LocalKey import id.walt.crypto.keys.LocalKeyMetadata import id.walt.crypto.utils.MultiBaseUtils import id.walt.crypto.utils.MultiCodecUtils +import love.forte.plugin.suspendtrans.annotation.JsPromise +import love.forte.plugin.suspendtrans.annotation.JvmAsync +import love.forte.plugin.suspendtrans.annotation.JvmBlocking +import kotlin.js.ExperimentalJsExport +import kotlin.js.JsExport +@ExperimentalJsExport +@JsExport object KeyUtils { + @JvmBlocking + @JvmAsync + @JsPromise + @JsExport.Ignore suspend fun fromPublicKeyMultiBase(identifier: String): Result { val publicKeyRaw = MultiBaseUtils.convertMultiBase58BtcToRawKey(identifier) //TODO: externalize import call diff --git a/waltid-did/src/commonMain/kotlin/id/walt/did/utils/VerificationMaterial.kt b/waltid-did/src/commonMain/kotlin/id/walt/did/utils/VerificationMaterial.kt index 7c9247a40..421ddb670 100644 --- a/waltid-did/src/commonMain/kotlin/id/walt/did/utils/VerificationMaterial.kt +++ b/waltid-did/src/commonMain/kotlin/id/walt/did/utils/VerificationMaterial.kt @@ -2,7 +2,11 @@ package id.walt.did.utils import id.walt.crypto.utils.JsonUtils.toJsonElement import kotlinx.serialization.json.* +import kotlin.js.ExperimentalJsExport +import kotlin.js.JsExport +@ExperimentalJsExport +@JsExport object VerificationMaterial { private val verificationMethods = arrayOf( "verificationMethod", diff --git a/waltid-did/src/jsMain/kotlin/bs58/bs58.kt b/waltid-did/src/jsMain/kotlin/bs58/bs58.kt new file mode 100644 index 000000000..898e2d026 --- /dev/null +++ b/waltid-did/src/jsMain/kotlin/bs58/bs58.kt @@ -0,0 +1,8 @@ +import org.khronos.webgl.Uint8Array + +@JsModule("bs58") +@JsNonModule +external object bs58 { + fun encode(data: Uint8Array): String + fun decode(base58String: String): Uint8Array +} \ No newline at end of file diff --git a/waltid-did/src/jsMain/kotlin/id/walt/did/utils/EncodingUtils.kt b/waltid-did/src/jsMain/kotlin/id/walt/did/utils/EncodingUtils.kt deleted file mode 100644 index 248d085a5..000000000 --- a/waltid-did/src/jsMain/kotlin/id/walt/did/utils/EncodingUtils.kt +++ /dev/null @@ -1,28 +0,0 @@ -package id.walt.did.utils - -import kotlin.js.js - -actual object EncodingUtils { - actual fun urlEncode(path: String): String = js("encodeURIComponent")(path) - - actual fun urlDecode(path: String): String = js("decodeURIComponent")(path) - - actual fun base64Encode(data: ByteArray): String = js("btoa")(data) - - actual fun base64Decode(data: String): ByteArray = js("atob")(data) - actual fun base58Encode(byteArray: ByteArray): String { - TODO("Not yet implemented") - } - - actual fun base58Decode(base58String: String): ByteArray { - TODO("Not yet implemented") - } - - actual fun fromHexString(hexString: String): ByteArray { - TODO("Not yet implemented") - } - - actual fun toHexString(byteArray: ByteArray): String { - TODO("Not yet implemented") - } -} \ No newline at end of file diff --git a/waltid-did/src/jsMain/kotlin/id/walt/did/utils/JsonCanonicalization.kt b/waltid-did/src/jsMain/kotlin/id/walt/did/utils/JsonCanonicalization.kt index aeee192c0..76c341dbd 100644 --- a/waltid-did/src/jsMain/kotlin/id/walt/did/utils/JsonCanonicalization.kt +++ b/waltid-did/src/jsMain/kotlin/id/walt/did/utils/JsonCanonicalization.kt @@ -3,6 +3,8 @@ package id.walt.did.utils import canonicalize import io.ktor.utils.io.core.* +@ExperimentalJsExport +@JsExport actual object JsonCanonicalization { actual fun getCanonicalBytes(json: String): ByteArray = canonicalize(json).toByteArray() actual fun getCanonicalString(json: String): String = canonicalize(json) diff --git a/waltid-did/src/jsMain/kotlin/id/walt/did/utils/UUID.kt b/waltid-did/src/jsMain/kotlin/id/walt/did/utils/UUID.kt index 681632a12..0dc4c8c16 100644 --- a/waltid-did/src/jsMain/kotlin/id/walt/did/utils/UUID.kt +++ b/waltid-did/src/jsMain/kotlin/id/walt/did/utils/UUID.kt @@ -2,4 +2,6 @@ package id.walt.did.utils import uuid +@ExperimentalJsExport +@JsExport actual fun randomUUID(): String = uuid.v4() \ No newline at end of file diff --git a/waltid-did/src/jvmMain/kotlin/id/walt/did/utils/EncodingUtils.kt b/waltid-did/src/jvmMain/kotlin/id/walt/did/utils/EncodingUtils.kt deleted file mode 100644 index 6a3e74c35..000000000 --- a/waltid-did/src/jvmMain/kotlin/id/walt/did/utils/EncodingUtils.kt +++ /dev/null @@ -1,24 +0,0 @@ -package id.walt.did.utils - -import io.ipfs.multibase.Base58 -import java.net.URLDecoder -import java.net.URLEncoder -import java.nio.charset.StandardCharsets - -actual object EncodingUtils { - actual fun urlEncode(path: String): String = URLEncoder.encode(path, StandardCharsets.UTF_8) - - actual fun urlDecode(path: String): String = URLDecoder.decode(path, StandardCharsets.UTF_8) - - actual fun base64Encode(data: ByteArray): String = java.util.Base64.getEncoder().encodeToString(data) - - actual fun base64Decode(data: String): ByteArray = java.util.Base64.getDecoder().decode(data) - actual fun base58Encode(byteArray: ByteArray): String = Base58.encode(byteArray) - - actual fun base58Decode(base58String: String): ByteArray = Base58.decode(base58String) - actual fun fromHexString(hexString: String): ByteArray = - hexString.replace(" ", "").chunked(2).map { it.toInt(16).toByte() }.toByteArray() - - actual fun toHexString(byteArray: ByteArray): String = - byteArray.joinToString("") { String.format("%02X ", (it.toInt() and 0xFF)) } -} diff --git a/waltid-did/src/jvmTest/kotlin/DidCreationTest.kt b/waltid-did/src/jvmTest/kotlin/DidCreationTest.kt index e83975346..5cf68d404 100644 --- a/waltid-did/src/jvmTest/kotlin/DidCreationTest.kt +++ b/waltid-did/src/jvmTest/kotlin/DidCreationTest.kt @@ -1,9 +1,12 @@ +import id.walt.did.dids.registrar.dids.DidKeyCreateOptions import id.walt.did.dids.registrar.dids.DidWebCreateOptions +import id.walt.did.dids.registrar.local.key.DidKeyRegistrar import id.walt.did.dids.registrar.local.web.DidWebRegistrar import kotlinx.coroutines.test.runTest import kotlinx.serialization.json.Json import kotlinx.serialization.json.jsonArray import kotlinx.serialization.json.jsonObject +import kotlinx.serialization.json.jsonPrimitive import kotlin.test.* class DidCreationTest { @@ -50,4 +53,19 @@ class DidCreationTest { assertTrue { didDoc2["@context"]!!.jsonArray.isNotEmpty() } } + @Test + fun checkReferencedDidMethods() = runTest { + val resultWeb = registrar + .register(DidWebCreateOptions("localhost", "/abc/xyz")) + val resultKey = DidKeyRegistrar().register(DidKeyCreateOptions()) + + arrayOf(resultKey, resultWeb).forEach { result -> + val didDoc1 = result.didDocument.toString() + println("DID doc: $didDoc1") + val id = + result.didDocument["verificationMethod"]?.jsonArray?.get(0)?.jsonObject?.get("id")?.jsonPrimitive?.content + val refId = result.didDocument["assertionMethod"]?.jsonArray?.get(0)?.jsonPrimitive?.content + assertEquals(id, refId) + } + } } diff --git a/waltid-did/src/jvmTest/kotlin/registrars/DidWebRegistrarTest.kt b/waltid-did/src/jvmTest/kotlin/registrars/DidWebRegistrarTest.kt index 4701a95c3..32480058a 100644 --- a/waltid-did/src/jvmTest/kotlin/registrars/DidWebRegistrarTest.kt +++ b/waltid-did/src/jvmTest/kotlin/registrars/DidWebRegistrarTest.kt @@ -7,9 +7,9 @@ import id.walt.did.dids.DidUtils import id.walt.did.dids.registrar.dids.DidCreateOptions import id.walt.did.dids.registrar.dids.DidWebCreateOptions import id.walt.did.dids.registrar.local.web.DidWebRegistrar -import id.walt.did.utils.EncodingUtils import id.walt.did.utils.ExtensionMethods.ensurePrefix import kotlinx.coroutines.runBlocking +import net.thauvin.erik.urlencoder.UrlEncoderUtil import org.junit.jupiter.params.ParameterizedTest import org.junit.jupiter.params.provider.Arguments import org.junit.jupiter.params.provider.Arguments.arguments @@ -91,7 +91,7 @@ class DidWebRegistrarTest : DidRegistrarTestBase(DidWebRegistrar()) { // assert [did identifier] and [domain + path] are identical assert( //TODO: avoid computations in result comparison - EncodingUtils.urlDecode(DidUtils.identifierFromDid(did)!!) == domain.plus( + UrlEncoderUtil.decode(DidUtils.identifierFromDid(did)!!) == domain.plus( path.takeIf { !it.isNullOrEmpty() }?.ensurePrefix("/")?.replace("/", ":") ?: "" ) ) diff --git a/waltid-did/src/jvmTest/kotlin/resolvers/UniResolverTest.kt b/waltid-did/src/jvmTest/kotlin/resolvers/UniResolverTest.kt index 9edc72211..4fc4fcaae 100644 --- a/waltid-did/src/jvmTest/kotlin/resolvers/UniResolverTest.kt +++ b/waltid-did/src/jvmTest/kotlin/resolvers/UniResolverTest.kt @@ -111,4 +111,4 @@ class UniResolverTest { runBlocking { UniresolverResolver().getSupportedMethods() } }.fold(onSuccess = { it.isSuccess }, onFailure = { false }) } -} \ No newline at end of file +} diff --git a/waltid-issuer-api/build.gradle.kts b/waltid-issuer-api/build.gradle.kts index f1d908385..7e88f6095 100644 --- a/waltid-issuer-api/build.gradle.kts +++ b/waltid-issuer-api/build.gradle.kts @@ -1,4 +1,3 @@ -import com.github.benmanes.gradle.versions.updates.DependencyUpdatesTask import org.jetbrains.kotlin.gradle.tasks.KotlinCompile object Versions { @@ -198,11 +197,3 @@ publishing { renderers = arrayOf(InventoryHtmlReportRenderer("xyzkit-licenses-report.html", "XYZ Kit")) filters = arrayOf(LicenseBundleNormalizer()) }*/ - - - -tasks.withType { - rejectVersionIf { - listOf("-beta", "-alpha", "-rc").any { it in candidate.version.lowercase() } || candidate.version.takeLast(4).contains("RC") - } -} diff --git a/waltid-mdoc-credentials/build.gradle.kts b/waltid-mdoc-credentials/build.gradle.kts index 0343d840e..e2495d68d 100644 --- a/waltid-mdoc-credentials/build.gradle.kts +++ b/waltid-mdoc-credentials/build.gradle.kts @@ -68,8 +68,8 @@ kotlin { } val jvmTest by getting { dependencies { - implementation("org.bouncycastle:bcprov-jdk15to18:1.76") - implementation("org.bouncycastle:bcpkix-jdk15to18:1.76") + implementation("org.bouncycastle:bcprov-lts8on:2.73.4") + implementation("org.bouncycastle:bcpkix-lts8on:2.73.4") implementation("io.mockk:mockk:1.13.2") implementation("io.kotest:kotest-runner-junit5:5.5.5") @@ -127,10 +127,3 @@ npmPublish { } } } - - -tasks.withType { - rejectVersionIf { - listOf("-beta", "-alpha", "-rc").any { it in candidate.version.lowercase() } || candidate.version.takeLast(4).contains("RC") - } -} diff --git a/waltid-openid4vc/build.gradle.kts b/waltid-openid4vc/build.gradle.kts index 89b8279d2..fabf68056 100644 --- a/waltid-openid4vc/build.gradle.kts +++ b/waltid-openid4vc/build.gradle.kts @@ -14,11 +14,11 @@ group = "id.walt" repositories { mavenCentral() - maven("https://jitpack.io") { + /*maven("https://jitpack.io") { content { includeGroup("com.github.multiformats") } - } + }*/ maven("https://repo.danubetech.com/repository/maven-public/") maven("https://maven.walt.id/repository/waltid/") { content { @@ -62,7 +62,7 @@ kotlin { } } } - nodejs() { + nodejs { generateTypeScriptDefinitions() } binaries.library() @@ -117,14 +117,14 @@ kotlin { implementation("io.kotest:kotest-assertions-json:5.8.0") implementation("com.google.crypto.tink:tink:1.12.0") // for JOSE using Ed25519 // Multibase - implementation("com.github.multiformats:java-multibase:v1.1.1") + // implementation("com.github.multiformats:java-multibase:v1.1.1") // TODO: current version implementation("id.walt:waltid-ssikit:1.2311131043.0") //implementation("id.walt:waltid-ssikit:1.JWTTYP") { // exclude("waltid-sd-jwt-jvm") // exclude(module = "waltid-sd-jwt-jvm") //} - implementation("org.bouncycastle:bcprov-jdk18on:1.77") // for secp256k1 (which was removed with Java 17) - implementation("org.bouncycastle:bcpkix-jdk18on:1.77") // PEM import + implementation("org.bouncycastle:bcprov-lts8on:2.73.4") // for secp256k1 (which was removed with Java 17) + implementation("org.bouncycastle:bcpkix-lts8on:2.73.4") // PEM import implementation("io.github.oshai:kotlin-logging-jvm:6.0.3") implementation("io.ktor:ktor-server-core-jvm:$ktor_version") @@ -206,11 +206,3 @@ npmPublish { } } } - - - -tasks.withType { - rejectVersionIf { - listOf("-beta", "-alpha", "-rc").any { it in candidate.version.lowercase() } || candidate.version.takeLast(4).contains("RC") - } -} diff --git a/waltid-reporting/build.gradle.kts b/waltid-reporting/build.gradle.kts index 611ef740d..05000f87f 100644 --- a/waltid-reporting/build.gradle.kts +++ b/waltid-reporting/build.gradle.kts @@ -118,11 +118,3 @@ kotlin { } } } - - - -tasks.withType { - rejectVersionIf { - listOf("-beta", "-alpha", "-rc").any { it in candidate.version.lowercase() } || candidate.version.takeLast(4).contains("RC") - } -} diff --git a/waltid-sdjwt/README.md b/waltid-sdjwt/README.md index 628fe3a03..f663d7529 100644 --- a/waltid-sdjwt/README.md +++ b/waltid-sdjwt/README.md @@ -71,7 +71,7 @@ specification: [draft-ietf-oauth-selective-disclosure-jwt-04](https://datatrack [...] id.walt -waltid-sd-jwt-jvm +waltid-sdjwt-jvm [ version ] ``` @@ -89,7 +89,7 @@ repositories { val sdJwtVersion = "1.2306071235.0" [...] dependencies { - implementation("id.walt:waltid-sd-jwt-jvm:$sdJwtVersion") + implementation("id.walt:waltid-sdjwt-jvm:$sdJwtVersion") } ``` diff --git a/waltid-sdjwt/build.gradle.kts b/waltid-sdjwt/build.gradle.kts index ff5830f94..c54b94b4d 100644 --- a/waltid-sdjwt/build.gradle.kts +++ b/waltid-sdjwt/build.gradle.kts @@ -65,7 +65,6 @@ kotlin { if (hostOs in listOf("Windows", "Linux") && hostArch == "aarch64") { println("Native compilation is not yet supported for aarch64 on Windows / Linux.") } else { - println("Running with $hostOs under $hostArch") val isMingwX64 = hostOs.startsWith("Windows") val nativeTarget = when { hostOs == "Mac OS X" -> macosX64("native") @@ -83,6 +82,7 @@ kotlin { else -> listOf() }.forEach { + println("Native compilation for target: ${it.name}") val platform = when (it.name) { "iosArm64" -> "iphoneos" else -> "iphonesimulator" @@ -214,11 +214,3 @@ npmPublish { } } } - - - -tasks.withType { - rejectVersionIf { - listOf("-beta", "-alpha", "-rc").any { it in candidate.version.lowercase() } || candidate.version.takeLast(4).contains("RC") - } -} diff --git a/waltid-verifiable-credentials/build.gradle.kts b/waltid-verifiable-credentials/build.gradle.kts index 4730798c3..54bf70a31 100644 --- a/waltid-verifiable-credentials/build.gradle.kts +++ b/waltid-verifiable-credentials/build.gradle.kts @@ -29,7 +29,6 @@ suspendTransform { js { }*/ - useJvmDefault() useJsDefault() } @@ -182,11 +181,3 @@ npmPublish { } } } - - - -tasks.withType { - rejectVersionIf { - listOf("-beta", "-alpha", "-rc").any { it in candidate.version.lowercase() } || candidate.version.takeLast(4).contains("RC") - } -} diff --git a/waltid-verifiable-credentials/src/commonMain/kotlin/id/walt/credentials/utils/EncodingUtils.kt b/waltid-verifiable-credentials/src/commonMain/kotlin/id/walt/credentials/utils/EncodingUtils.kt deleted file mode 100644 index f32b4c571..000000000 --- a/waltid-verifiable-credentials/src/commonMain/kotlin/id/walt/credentials/utils/EncodingUtils.kt +++ /dev/null @@ -1,6 +0,0 @@ -package id.walt.credentials.utils - -expect object EncodingUtils { - fun urlEncode(path: String): String - fun urlDecode(path: String): String -} diff --git a/waltid-verifiable-credentials/src/jsMain/kotlin/id/walt/credentials/utils/EncodingUtils.kt b/waltid-verifiable-credentials/src/jsMain/kotlin/id/walt/credentials/utils/EncodingUtils.kt deleted file mode 100644 index d3c311141..000000000 --- a/waltid-verifiable-credentials/src/jsMain/kotlin/id/walt/credentials/utils/EncodingUtils.kt +++ /dev/null @@ -1,8 +0,0 @@ -package id.walt.credentials.utils -@OptIn(ExperimentalJsExport::class) -@JsExport -actual object EncodingUtils { - actual fun urlEncode(path: String): String = js("encodeURIComponent")(path) - - actual fun urlDecode(path: String): String = js("decodeURIComponent")(path) -} diff --git a/waltid-verifiable-credentials/src/jvmMain/kotlin/id/walt/credentials/utils/EncodingUtils.kt b/waltid-verifiable-credentials/src/jvmMain/kotlin/id/walt/credentials/utils/EncodingUtils.kt deleted file mode 100644 index f062b8216..000000000 --- a/waltid-verifiable-credentials/src/jvmMain/kotlin/id/walt/credentials/utils/EncodingUtils.kt +++ /dev/null @@ -1,10 +0,0 @@ -package id.walt.credentials.utils - -import java.net.URLDecoder -import java.net.URLEncoder - -actual object EncodingUtils { - actual fun urlEncode(path: String): String = URLEncoder.encode(path, Charsets.UTF_8) - - actual fun urlDecode(path: String): String = URLDecoder.decode(path, Charsets.UTF_8) -} diff --git a/waltid-verifier-api/build.gradle.kts b/waltid-verifier-api/build.gradle.kts index fdb04a4c6..9dd112f12 100644 --- a/waltid-verifier-api/build.gradle.kts +++ b/waltid-verifier-api/build.gradle.kts @@ -194,10 +194,3 @@ publishing { // renderers = arrayOf(InventoryHtmlReportRenderer("waltid-verifier-licenses-report.html", "walt.id verifier")) // filters = arrayOf(LicenseBundleNormalizer()) //} - - -tasks.withType { - rejectVersionIf { - listOf("-beta", "-alpha", "-rc").any { it in candidate.version.lowercase() } || candidate.version.takeLast(4).contains("RC") - } -} diff --git a/waltid-wallet-api/Dockerfile b/waltid-wallet-api/Dockerfile index 19bf0054c..6d0510037 100644 --- a/waltid-wallet-api/Dockerfile +++ b/waltid-wallet-api/Dockerfile @@ -5,6 +5,7 @@ COPY gradle/ /work/gradle COPY settings.gradle.kts build.gradle.kts gradle.properties gradlew /work/ COPY waltid-openid4vc/build.gradle.kts /work/waltid-openid4vc/ COPY waltid-sdjwt/build.gradle.kts /work/waltid-sdjwt/ +COPY waltid-crypto/build.gradle.kts /work/waltid-crypto/ COPY waltid-did/build.gradle.kts /work/waltid-did/ WORKDIR /work/waltid-wallet-api/ @@ -13,6 +14,7 @@ RUN gradle build || return 0 COPY waltid-openid4vc/. /work/waltid-openid4vc COPY waltid-sdjwt/. /work/waltid-sdjwt +COPY waltid-crypto/. /work/waltid-crypto COPY waltid-did/. /work/waltid-did COPY waltid-wallet-api/src/ /work/waltid-wallet-api/src diff --git a/waltid-wallet-api/build.gradle.kts b/waltid-wallet-api/build.gradle.kts index e2637d992..754748c46 100644 --- a/waltid-wallet-api/build.gradle.kts +++ b/waltid-wallet-api/build.gradle.kts @@ -99,14 +99,15 @@ dependencies { /* -- Security -- */ // Bouncy Castle - implementation("org.bouncycastle:bcprov-jdk18on:1.77") + implementation("org.bouncycastle:bcprov-lts8on:2.73.4") // Argon2 implementation("de.mkammerer:argon2-jvm:2.11") // waltid-did - implementation(project(":waltid-did"))//id.walt.crypto provided by id.walt.did:waltid-did + implementation(project(":waltid-crypto")) + implementation(project(":waltid-did")) // OIDC implementation(project(":waltid-openid4vc")) @@ -159,11 +160,3 @@ dependencies { testImplementation("io.kotest.extensions:kotest-assertions-ktor:2.0.0")*/ testImplementation("io.ktor:ktor-server-tests-jvm:$ktorVersion") } - - - -tasks.withType { - rejectVersionIf { - listOf("-beta", "-alpha", "-rc").any { it in candidate.version.lowercase() } || candidate.version.takeLast(4).contains("RC") - } -} diff --git a/waltid-web-wallet/src/components/credentials/VerifiableCredentialCard.vue b/waltid-web-wallet/src/components/credentials/VerifiableCredentialCard.vue index 04047a7f3..9173e6620 100644 --- a/waltid-web-wallet/src/components/credentials/VerifiableCredentialCard.vue +++ b/waltid-web-wallet/src/components/credentials/VerifiableCredentialCard.vue @@ -57,7 +57,7 @@ const props = defineProps({ const credential = props.credential?.parsedDocument; const isDetailView = props.isDetailView ?? false; const manifest = props.credential?.manifest != "{}" ? props.credential?.manifest : null -const manifestDisplay = manifest ? JSON.parse(manifest)?.display : null; +const manifestDisplay = manifest ? (typeof manifest === 'string' ? JSON.parse(manifest) : manifest)?.display : null; const manifestCard = manifestDisplay?.card; const title = manifestDisplay?.title ?? credential.type?.at(-1); diff --git a/waltid-web-wallet/src/pages/wallet/[wallet]/credentials/[credentialId].vue b/waltid-web-wallet/src/pages/wallet/[wallet]/credentials/[credentialId].vue index 1515ad027..1e39e0140 100644 --- a/waltid-web-wallet/src/pages/wallet/[wallet]/credentials/[credentialId].vue +++ b/waltid-web-wallet/src/pages/wallet/[wallet]/credentials/[credentialId].vue @@ -403,7 +403,7 @@ type WalletCredential = { const { data: credential, pending, refresh, error } = await useLazyFetch(`/wallet-api/wallet/${currentWallet.value}/credentials/${encodeURIComponent(credentialId)}`); refreshNuxtData(); -const manifest = computed(() => (credential.value?.manifest && credential.value?.manifest != "{}" ? JSON.parse(credential.value?.manifest) : null)); +const manifest = computed(() => (credential.value?.manifest && credential.value?.manifest != "{}" ? (typeof credential.value?.manifest === 'string' ? JSON.parse(credential.value?.manifest) : credential.value?.manifest) : null)); const manifestClaims = computed(() => manifest.value?.display?.claims); const issuerName = ref(null);