Skip to content
Closed
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions cryptography-jose/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/*
* Copyright (c) 2023-2025 Oleg Yukhnevich. Use of this source code is governed by the Apache 2.0 license.
*/

import ckbuild.*

plugins {
id("ckbuild.multiplatform-library")
alias(libs.plugins.kotlin.plugin.serialization)
}

description = "cryptography-kotlin JOSE support"

kotlin {
jvmTarget()
jsTarget()
nativeTargets()
wasmTargets()

sourceSets.commonMain.dependencies {
api(projects.cryptographyCore)
implementation(libs.kotlinx.serialization.json)
}

sourceSets.commonTest.dependencies {
implementation(libs.kotlin.test)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
/*
* Copyright (c) 2023-2025 Oleg Yukhnevich. Use of this source code is governed by the Apache 2.0 license.
*/

package dev.cryptography.jose

/**
* JSON Web Algorithms (JWA) constants as defined in RFC 7518.
*
* This object contains constants for cryptographic algorithms used in JOSE.
*/
public object JsonWebAlgorithms {
// Digital Signature or MAC Algorithms for JWS (Section 3.1)
public object Signature {
/** HMAC using SHA-256 */
public const val HS256: String = "HS256"
/** HMAC using SHA-384 */
public const val HS384: String = "HS384"
/** HMAC using SHA-512 */
public const val HS512: String = "HS512"
/** RSASSA-PKCS1-v1_5 using SHA-256 */
public const val RS256: String = "RS256"
/** RSASSA-PKCS1-v1_5 using SHA-384 */
public const val RS384: String = "RS384"
/** RSASSA-PKCS1-v1_5 using SHA-512 */
public const val RS512: String = "RS512"
/** ECDSA using P-256 and SHA-256 */
public const val ES256: String = "ES256"
/** ECDSA using P-384 and SHA-384 */
public const val ES384: String = "ES384"
/** ECDSA using P-521 and SHA-512 */
public const val ES512: String = "ES512"
/** RSASSA-PSS using SHA-256 and MGF1 with SHA-256 */
public const val PS256: String = "PS256"
/** RSASSA-PSS using SHA-384 and MGF1 with SHA-384 */
public const val PS384: String = "PS384"
/** RSASSA-PSS using SHA-512 and MGF1 with SHA-512 */
public const val PS512: String = "PS512"
/** No digital signature or MAC performed */
public const val NONE: String = "none"
}

// Key Management Algorithms for JWE (Section 4.1)
public object KeyManagement {
/** RSAES-PKCS1-v1_5 */
public const val RSA1_5: String = "RSA1_5"
/** RSAES OAEP using default parameters */
public const val RSA_OAEP: String = "RSA-OAEP"
/** RSAES OAEP using SHA-256 and MGF1 with SHA-256 */
public const val RSA_OAEP_256: String = "RSA-OAEP-256"
/** AES Key Wrap with default initial value using 128-bit key */
public const val A128KW: String = "A128KW"
/** AES Key Wrap with default initial value using 192-bit key */
public const val A192KW: String = "A192KW"
/** AES Key Wrap with default initial value using 256-bit key */
public const val A256KW: String = "A256KW"
/** Direct use of a shared symmetric key as the CEK */
public const val DIR: String = "dir"
/** Elliptic Curve Diffie-Hellman Ephemeral Static key agreement using Concat KDF */
public const val ECDH_ES: String = "ECDH-ES"
/** ECDH Ephemeral Static key agreement using Concat KDF and CEK wrapped with AES Key Wrap using a 128-bit key */
public const val ECDH_ES_A128KW: String = "ECDH-ES+A128KW"
/** ECDH Ephemeral Static key agreement using Concat KDF and CEK wrapped with AES Key Wrap using a 192-bit key */
public const val ECDH_ES_A192KW: String = "ECDH-ES+A192KW"
/** ECDH Ephemeral Static key agreement using Concat KDF and CEK wrapped with AES Key Wrap using a 256-bit key */
public const val ECDH_ES_A256KW: String = "ECDH-ES+A256KW"
/** AES GCM key encryption with a 128-bit key */
public const val A128GCMKW: String = "A128GCMKW"
/** AES GCM key encryption with a 192-bit key */
public const val A192GCMKW: String = "A192GCMKW"
/** AES GCM key encryption with a 256-bit key */
public const val A256GCMKW: String = "A256GCMKW"
/** PBES2 with HMAC SHA-256 and AES Key Wrap with 128-bit key */
public const val PBES2_HS256_A128KW: String = "PBES2-HS256+A128KW"
/** PBES2 with HMAC SHA-384 and AES Key Wrap with 192-bit key */
public const val PBES2_HS384_A192KW: String = "PBES2-HS384+A192KW"
/** PBES2 with HMAC SHA-512 and AES Key Wrap with 256-bit key */
public const val PBES2_HS512_A256KW: String = "PBES2-HS512+A256KW"
}

// Content Encryption Algorithms for JWE (Section 5.1)
public object ContentEncryption {
/** AES_128_CBC_HMAC_SHA_256 authenticated encryption algorithm */
public const val A128CBC_HS256: String = "A128CBC-HS256"
/** AES_192_CBC_HMAC_SHA_384 authenticated encryption algorithm */
public const val A192CBC_HS384: String = "A192CBC-HS384"
/** AES_256_CBC_HMAC_SHA_512 authenticated encryption algorithm */
public const val A256CBC_HS512: String = "A256CBC-HS512"
/** AES GCM using 128-bit key */
public const val A128GCM: String = "A128GCM"
/** AES GCM using 192-bit key */
public const val A192GCM: String = "A192GCM"
/** AES GCM using 256-bit key */
public const val A256GCM: String = "A256GCM"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
/*
* Copyright (c) 2023-2025 Oleg Yukhnevich. Use of this source code is governed by the Apache 2.0 license.
*/

package dev.cryptography.jose

import kotlinx.serialization.Serializable
import kotlinx.serialization.json.JsonElement

/**
* JSON Web Key (JWK) as defined in RFC 7517.
*
* A JWK is a JSON object that represents a cryptographic key.
*/
@Serializable
public data class JsonWebKey(
/** Key Type - identifies the cryptographic algorithm family used with the key */
val kty: String,
/** Public Key Use - identifies the intended use of the public key */
val use: String? = null,
/** Key Operations - identifies the operation(s) for which the key is intended to be used */
val key_ops: List<String>? = null,
/** Algorithm - identifies the algorithm intended for use with the key */
val alg: String? = null,
/** Key ID - used to match a specific key among multiple keys */
val kid: String? = null,
/** X.509 URL - URI that refers to a resource for an X.509 public key certificate or certificate chain */
val x5u: String? = null,
/** X.509 Certificate Chain - chain of one or more PKIX certificates */
val x5c: List<String>? = null,
/** X.509 Certificate SHA-1 Thumbprint */
val x5t: String? = null,
/** X.509 Certificate SHA-256 Thumbprint */
@Serializable(with = kotlinx.serialization.json.JsonElementSerializer::class)
val x5t_S256: JsonElement? = null,
/** Additional key-specific parameters */
val additionalParameters: Map<String, JsonElement> = emptyMap()
) {
companion object {
// Key Types as defined in RFC 7518
public const val KEY_TYPE_RSA: String = "RSA"
public const val KEY_TYPE_EC: String = "EC"
public const val KEY_TYPE_SYMMETRIC: String = "oct"

// Public Key Use values
public const val USE_SIGNATURE: String = "sig"
public const val USE_ENCRYPTION: String = "enc"

// Key Operations
public const val OP_SIGN: String = "sign"
public const val OP_VERIFY: String = "verify"
public const val OP_ENCRYPT: String = "encrypt"
public const val OP_DECRYPT: String = "decrypt"
public const val OP_WRAP_KEY: String = "wrapKey"
public const val OP_UNWRAP_KEY: String = "unwrapKey"
public const val OP_DERIVE_KEY: String = "deriveKey"
public const val OP_DERIVE_BITS: String = "deriveBits"
}
}

/**
* JSON Web Key Set (JWK Set) as defined in RFC 7517.
*
* A JWK Set is a JSON object that represents a set of JWKs.
*/
@Serializable
public data class JsonWebKeySet(
/** Array of JWK values */
val keys: List<JsonWebKey>
) {
/**
* Finds a key by its Key ID (kid).
*/
public fun findByKeyId(kid: String): JsonWebKey? = keys.find { it.kid == kid }

/**
* Finds keys by their intended use.
*/
public fun findByUse(use: String): List<JsonWebKey> = keys.filter { it.use == use }

/**
* Finds keys by their algorithm.
*/
public fun findByAlgorithm(alg: String): List<JsonWebKey> = keys.filter { it.alg == alg }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
/*
* Copyright (c) 2023-2025 Oleg Yukhnevich. Use of this source code is governed by the Apache 2.0 license.
*/

package dev.cryptography.jose

import kotlinx.serialization.Serializable
import kotlinx.serialization.json.JsonElement

/**
* JSON Web Token (JWT) representation as defined in RFC 7519.
*
* A JWT consists of three parts separated by dots:
* - Header: contains metadata about the token
* - Payload: contains the claims
* - Signature: ensures the token hasn't been tampered with
*/
@Serializable
public data class JsonWebToken(
val header: JwtHeader,
val payload: JwtPayload,
val signature: String? = null
) {
/**
* Encodes the JWT as a compact serialization string.
* Format: base64url(header).base64url(payload).base64url(signature)
*/
public fun encode(): String {
// TODO: Implement JWT encoding
throw NotImplementedError("JWT encoding not yet implemented")
}

companion object {
/**
* Decodes a JWT from its compact serialization string.
*/
public fun decode(token: String): JsonWebToken {
// TODO: Implement JWT decoding
throw NotImplementedError("JWT decoding not yet implemented")
}
}
}

/**
* JWT Header as defined in RFC 7515.
*/
@Serializable
public data class JwtHeader(
/** Algorithm used for signing/encrypting the JWT */
val alg: String,
/** Type of the token, typically "JWT" */
val typ: String? = "JWT",
/** Key ID hint indicating which key was used to secure the JWT */
val kid: String? = null
)

/**
* JWT Payload containing claims as defined in RFC 7519.
*/
@Serializable
public data class JwtPayload(
/** Issuer - identifies the principal that issued the JWT */
val iss: String? = null,
/** Subject - identifies the principal that is the subject of the JWT */
val sub: String? = null,
/** Audience - identifies the recipients that the JWT is intended for */
val aud: String? = null,
/** Expiration Time - identifies the expiration time on or after which the JWT MUST NOT be accepted */
val exp: Long? = null,
/** Not Before - identifies the time before which the JWT MUST NOT be accepted */
val nbf: Long? = null,
/** Issued At - identifies the time at which the JWT was issued */
val iat: Long? = null,
/** JWT ID - provides a unique identifier for the JWT */
val jti: String? = null,
/** Additional custom claims */
val customClaims: Map<String, JsonElement> = emptyMap()
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
/*
* Copyright (c) 2023-2025 Oleg Yukhnevich. Use of this source code is governed by the Apache 2.0 license.
*/

package dev.cryptography.jose

import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertNotNull
import kotlin.test.assertNull

class JsonWebKeyTest {

@Test
fun testJwkCreation() {
val jwk = JsonWebKey(
kty = JsonWebKey.KEY_TYPE_RSA,
use = JsonWebKey.USE_SIGNATURE,
alg = JsonWebAlgorithms.Signature.RS256,
kid = "test-key-id"
)

assertEquals(JsonWebKey.KEY_TYPE_RSA, jwk.kty)
assertEquals(JsonWebKey.USE_SIGNATURE, jwk.use)
assertEquals(JsonWebAlgorithms.Signature.RS256, jwk.alg)
assertEquals("test-key-id", jwk.kid)
}

@Test
fun testJwkSetOperations() {
val key1 = JsonWebKey(
kty = JsonWebKey.KEY_TYPE_RSA,
use = JsonWebKey.USE_SIGNATURE,
alg = JsonWebAlgorithms.Signature.RS256,
kid = "key-1"
)

val key2 = JsonWebKey(
kty = JsonWebKey.KEY_TYPE_EC,
use = JsonWebKey.USE_ENCRYPTION,
alg = JsonWebAlgorithms.Signature.ES256,
kid = "key-2"
)

val jwkSet = JsonWebKeySet(keys = listOf(key1, key2))

// Test finding by key ID
val foundKey1 = jwkSet.findByKeyId("key-1")
assertNotNull(foundKey1)
assertEquals("key-1", foundKey1.kid)

val notFoundKey = jwkSet.findByKeyId("non-existent")
assertNull(notFoundKey)

// Test finding by use
val signatureKeys = jwkSet.findByUse(JsonWebKey.USE_SIGNATURE)
assertEquals(1, signatureKeys.size)
assertEquals("key-1", signatureKeys.first().kid)

// Test finding by algorithm
val rs256Keys = jwkSet.findByAlgorithm(JsonWebAlgorithms.Signature.RS256)
assertEquals(1, rs256Keys.size)
assertEquals("key-1", rs256Keys.first().kid)
}
}
Loading
Loading