JOSE and COSE Operations
The IDK supports two major cryptographic message formats: JOSE (JSON Object Signing and Encryption) and COSE (CBOR Object Signing and Encryption). JOSE is the primary format used in OpenID protocols (OID4VP, OID4VCI, SD-JWT), while COSE is used for ISO mDoc credentials.
Which format you use is typically dictated by the protocol, not personal preference. If you are issuing or verifying SD-JWT credentials through OID4VP/OID4VCI, you will use JOSE. If you are working with ISO 18013-5 mobile driving licenses (mDoc), you will use COSE. Many real-world deployments need both, which is why the IDK provides conversion utilities between them.
Format Overview
| Aspect | JOSE | COSE |
|---|---|---|
| Encoding | Text (JSON) | Binary (CBOR) |
| Size | Larger | Compact |
| Primary Use | OAuth, OpenID, Web APIs | mDoc, IoT |
| Key Format | JWK | COSE_Key |
| Signature | JWS | COSE_Sign1 |
| Encryption | JWE | COSE_Encrypt |
JwtService - JWS and JWT Operations
JWS (JSON Web Signature) provides integrity protection for a payload. A JWT is just a JWS whose payload happens to be a JSON claims set. Compact serialization (header.payload.signature) is what you will use most of the time, but the JSON serialization formats exist for cases where you need multiple signatures or want to keep the structure as regular JSON.
The JwtService is the primary service for creating and verifying JSON Web Signatures (JWS) and JSON Web Tokens (JWT). It provides a command-based API for all JWS operations.
Obtaining the Service
- Android/Kotlin
- iOS/Swift
val jwtService = session.graph.jwtService
// Access individual commands
val createJwsCompact = jwtService.commands.createJwsCompact
val createJwsJsonFlattened = jwtService.commands.createJwsJsonFlattened
val createJwsJsonGeneral = jwtService.commands.createJwsJsonGeneral
val verifyJws = jwtService.commands.verifyJws
let jwtService = session.graph.jwtService
// Access individual commands
let createJwsCompact = jwtService.commands.createJwsCompact
let createJwsJsonFlattened = jwtService.commands.createJwsJsonFlattened
let createJwsJsonGeneral = jwtService.commands.createJwsJsonGeneral
let verifyJws = jwtService.commands.verifyJws
Creating JWS with Existing Keys
The most common use case is signing with a key that already exists in your KMS. Reference keys by alias, key ID, or pass the key directly:
- Android/Kotlin
- iOS/Swift
// Reference key by alias (most common for KMS-managed keys)
val byAlias = ManagedOptsAlias(identifier = "my-signing-key")
// Reference key by key ID
val byKid = ManagedOptsKid(identifier = "key-123")
// Reference key by KeyInfo (with additional metadata)
val byKeyInfo = ManagedOptsKeyInfo(
identifier = KeyInfo(
alias = "my-signing-key",
providerId = "software",
signatureAlgorithm = SignatureAlgorithm.ECDSA_SHA256
)
)
// Pass an existing JWK directly
val byJwk = ManagedOptsJwk(identifier = existingJwkDto)
// Reference key by alias (most common for KMS-managed keys)
let byAlias = ManagedOptsAlias(identifier: "my-signing-key")
// Reference key by key ID
let byKid = ManagedOptsKid(identifier: "key-123")
// Reference key by KeyInfo (with additional metadata)
let byKeyInfo = ManagedOptsKeyInfo(
identifier: KeyInfo(
alias: "my-signing-key",
providerId: "software",
signatureAlgorithm: .ecdsaSha256
)
)
// Pass an existing JWK directly
let byJwk = ManagedOptsJwk(identifier: existingJwkDto)
Creating Compact JWS
Compact serialization produces the familiar header.payload.signature format:
- Android/Kotlin
- iOS/Swift
// Create payload
val payload = mapOf(
"iss" to "https://issuer.example.com",
"sub" to "user-123",
"aud" to "https://verifier.example.com",
"iat" to System.currentTimeMillis() / 1000,
"exp" to (System.currentTimeMillis() / 1000) + 3600,
"name" to "Alice Smith"
)
// Create compact JWS using key alias
val result = jwtService.createJwsCompact(
CreateJwsArgs(
issuer = ManagedOptsAlias("my-signing-key"),
payload = payload,
mode = JwsIdentifierMode.KID, // Include key ID in header
opts = CreateJwsOpts(
protectedHeader = buildJsonObject {
put("typ", "JWT")
}
)
)
)
if (result.isOk) {
val jwt = result.value.jwt
println("JWT: $jwt")
// eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6Im15LXNpZ25pbmcta2V5In0.eyJpc3MiOi...
} else {
println("Error: ${result.error.message}")
}
// Create payload
let payload: [String: Any] = [
"iss": "https://issuer.example.com",
"sub": "user-123",
"aud": "https://verifier.example.com",
"iat": Int64(Date().timeIntervalSince1970),
"exp": Int64(Date().timeIntervalSince1970) + 3600,
"name": "Alice Smith"
]
// Create compact JWS using key alias
let result = try await jwtService.createJwsCompact(
args: CreateJwsArgs(
issuer: ManagedOptsAlias(identifier: "my-signing-key"),
payload: payload,
mode: .kid, // Include key ID in header
opts: CreateJwsOpts(
protectedHeader: ["typ": "JWT"]
)
)
)
if result.isOk {
let jwt = result.value.jwt
print("JWT: \(jwt)")
} else {
print("Error: \(result.error.message)")
}
JWS Identifier Modes
Control how the signing key is identified in the JWS header:
| Mode | Header Field | Use Case |
|---|---|---|
KID | kid | Key ID reference - verifier looks up key |
JWK | jwk | Embed public key in header |
X5C | x5c | Embed X.509 certificate chain |
DID | kid (DID URL) | Decentralized identifier reference |
AUTO | Automatic | IDK selects based on key metadata |
- Android/Kotlin
- iOS/Swift
// Embed the public key in the header
val jwsWithEmbeddedKey = jwtService.createJwsCompact(
CreateJwsArgs(
issuer = ManagedOptsAlias("my-signing-key"),
payload = payload,
mode = JwsIdentifierMode.JWK // Embeds public JWK in header
)
)
// Include X.509 certificate chain
val jwsWithCertChain = jwtService.createJwsCompact(
CreateJwsArgs(
issuer = ManagedOptsKeyInfo(
identifier = KeyInfo(
alias = "my-signing-key",
x5c = arrayOf(base64EncodedCert)
)
),
payload = payload,
mode = JwsIdentifierMode.X5C
)
)
// Embed the public key in the header
let jwsWithEmbeddedKey = try await jwtService.createJwsCompact(
args: CreateJwsArgs(
issuer: ManagedOptsAlias(identifier: "my-signing-key"),
payload: payload,
mode: .jwk // Embeds public JWK in header
)
)
// Include X.509 certificate chain
let jwsWithCertChain = try await jwtService.createJwsCompact(
args: CreateJwsArgs(
issuer: ManagedOptsKeyInfo(
identifier: KeyInfo(
alias: "my-signing-key",
x5c: [base64EncodedCert]
)
),
payload: payload,
mode: .x5c
)
)
Creating JSON Serialized JWS
JWS JSON serialization comes in two flavors. Flattened is a single-signature JSON object, useful when you need JSON structure but only one signer. General supports an array of signatures, which lets multiple parties sign the same payload independently.
- Android/Kotlin
- iOS/Swift
// JSON Flattened - single signature in JSON format
val flattenedResult = jwtService.createJwsJsonFlattened(
CreateJwsJsonArgs(
issuer = ManagedOptsAlias("my-signing-key"),
payload = payload,
mode = JwsIdentifierMode.KID
)
)
// Result structure:
// {
// "payload": "eyJpc3MiOi...",
// "protected": "eyJhbGciOi...",
// "header": { "kid": "my-signing-key" },
// "signature": "DtEhU3lj..."
// }
// JSON General - supports multiple signatures
val generalResult = jwtService.createJwsJsonGeneral(
CreateJwsJsonArgs(
issuer = ManagedOptsAlias("primary-key"),
payload = payload,
mode = JwsIdentifierMode.KID,
existingSignatures = listOf(existingSignature) // Add to existing signatures
)
)
// Result structure:
// {
// "payload": "eyJpc3MiOi...",
// "signatures": [
// { "protected": "...", "header": {...}, "signature": "..." },
// { "protected": "...", "header": {...}, "signature": "..." }
// ]
// }
// JSON Flattened - single signature in JSON format
let flattenedResult = try await jwtService.createJwsJsonFlattened(
args: CreateJwsJsonArgs(
issuer: ManagedOptsAlias(identifier: "my-signing-key"),
payload: payload,
mode: .kid
)
)
// JSON General - supports multiple signatures
let generalResult = try await jwtService.createJwsJsonGeneral(
args: CreateJwsJsonArgs(
issuer: ManagedOptsAlias(identifier: "primary-key"),
payload: payload,
mode: .kid,
existingSignatures: [existingSignature]
)
)
Verifying JWS
Verify signatures using the verifier's key or by resolving the key from the JWS header:
- Android/Kotlin
- iOS/Swift
// Parse the JWS
val jws = Jws.fromCompact(jwtString)
// Verify with explicit key
val result = jwtService.verifyJws(
VerifyJwsArgs(
jws = jws,
identifier = ManagedOptsAlias("issuer-public-key")
)
)
if (result.isOk) {
val validation = result.value
if (validation.isValid) {
println("Signature valid")
println("Payload: ${jws.payload.decodeToString()}")
} else {
println("Invalid: ${validation.errorMessages.joinToString()}")
}
} else {
println("Verification error: ${result.error.message}")
}
// Verify using embedded JWK from header (if present)
val autoResult = jwtService.verifyJws(
VerifyJwsArgs(
jws = jws,
identifier = null // Key resolved from header
)
)
// Parse the JWS
let jws = Jws.fromCompact(compact: jwtString)
// Verify with explicit key
let result = try await jwtService.verifyJws(
args: VerifyJwsArgs(
jws: jws,
identifier: ManagedOptsAlias(identifier: "issuer-public-key")
)
)
if result.isOk {
let validation = result.value
if validation.isValid {
print("Signature valid")
print("Payload: \(String(data: jws.payload, encoding: .utf8)!)")
} else {
print("Invalid: \(validation.errorMessages.joined(separator: ", "))")
}
} else {
print("Verification error: \(result.error.message)")
}
// Verify using embedded JWK from header (if present)
let autoResult = try await jwtService.verifyJws(
args: VerifyJwsArgs(
jws: jws,
identifier: nil // Key resolved from header
)
)
JweService - JWE Encryption Operations
While JWS protects integrity (anyone can read the payload, but nobody can tamper with it), JWE protects confidentiality. JWE encrypts the payload so only the intended recipient can read it. The two-step process (prepare, then create) separates key agreement from serialization, which matters when encrypting to multiple recipients with different key types.
The JweService handles JSON Web Encryption for encrypting content to one or more recipients.
Obtaining the Service
- Android/Kotlin
- iOS/Swift
val jweService = session.graph.jweService
// Access individual commands
val prepareJwe = jweService.commands.prepareJwe
val createJweCompact = jweService.commands.createJweCompact
val createJweJsonFlattened = jweService.commands.createJweJsonFlattened
val createJweJsonGeneral = jweService.commands.createJweJsonGeneral
val decryptJwe = jweService.commands.decryptJwe
let jweService = session.graph.jweService
// Access individual commands
let prepareJwe = jweService.commands.prepareJwe
let createJweCompact = jweService.commands.createJweCompact
let createJweJsonFlattened = jweService.commands.createJweJsonFlattened
let createJweJsonGeneral = jweService.commands.createJweJsonGeneral
let decryptJwe = jweService.commands.decryptJwe
JWE Algorithm Selection
JWE uses two algorithms: one for key encryption (wrapping the content encryption key so the recipient can unwrap it) and one for content encryption (the actual AES encryption of the payload). For most use cases, ECDH-ES+A256KW with A256GCM is a good default for EC keys, and RSA-OAEP-256 with A256GCM for RSA keys.
Key Encryption Algorithms:
| Algorithm | Description | Key Type |
|---|---|---|
RSA-OAEP | RSA with OAEP padding | RSA |
RSA-OAEP-256 | RSA-OAEP with SHA-256 | RSA |
ECDH-ES | Direct ECDH key agreement | EC |
ECDH-ES+A128KW | ECDH with AES key wrap | EC |
ECDH-ES+A256KW | ECDH with AES-256 key wrap | EC |
A128KW | AES key wrap (symmetric) | Symmetric |
A256KW | AES-256 key wrap (symmetric) | Symmetric |
dir | Direct encryption (no key wrap) | Symmetric |
Content Encryption Algorithms:
| Algorithm | Description |
|---|---|
A128GCM | AES-128 in GCM mode |
A192GCM | AES-192 in GCM mode |
A256GCM | AES-256 in GCM mode |
A128CBC-HS256 | AES-128-CBC with HMAC-SHA-256 |
A256CBC-HS512 | AES-256-CBC with HMAC-SHA-512 |
Creating Compact JWE
- Android/Kotlin
- iOS/Swift
val plaintext = "Sensitive data to encrypt".encodeToByteArray()
// Step 1: Prepare the JWE (generates CEK, builds headers)
val prepareResult = jweService.prepareJwe(
PrepareJweArgs(
plaintext = plaintext,
recipient = ManagedOptsAlias("recipient-public-key"),
keyEncryptionAlg = "RSA-OAEP-256",
contentEncryptionAlg = "A256GCM",
opts = CreateJweOpts(
compress = false
)
)
)
if (prepareResult.isOk) {
val prepared = prepareResult.value
// Step 2: Create the compact JWE
val jweResult = jweService.createJweCompact(
CreateJweCompactArgs(
preparedJwe = prepared,
aad = null // Optional additional authenticated data
)
)
if (jweResult.isOk) {
val jwe = jweResult.value
println("JWE: ${jwe.compact}")
// eyJhbGciOiJSU0EtT0FFUC0yNTYiLCJlbmMiOiJBMjU2R0NNIn0.OKOaw...
} else {
println("Encryption failed: ${jweResult.error.message}")
}
} else {
println("Preparation failed: ${prepareResult.error.message}")
}
let plaintext = "Sensitive data to encrypt".data(using: .utf8)!
// Step 1: Prepare the JWE (generates CEK, builds headers)
let prepareResult = try await jweService.prepareJwe(
args: PrepareJweArgs(
plaintext: plaintext,
recipient: ManagedOptsAlias(identifier: "recipient-public-key"),
keyEncryptionAlg: "RSA-OAEP-256",
contentEncryptionAlg: "A256GCM",
opts: CreateJweOpts(
compress: false
)
)
)
if prepareResult.isOk {
let prepared = prepareResult.value
// Step 2: Create the compact JWE
let jweResult = try await jweService.createJweCompact(
args: CreateJweCompactArgs(
preparedJwe: prepared,
aad: nil
)
)
if jweResult.isOk {
let jwe = jweResult.value
print("JWE: \(jwe.compact)")
} else {
print("Encryption failed: \(jweResult.error.message)")
}
} else {
print("Preparation failed: \(prepareResult.error.message)")
}
Creating Multi-Recipient JWE
JSON General serialization supports encrypting to multiple recipients:
- Android/Kotlin
- iOS/Swift
// Prepare for primary recipient
val prepareResult = jweService.prepareJwe(
PrepareJweArgs(
plaintext = plaintext,
recipient = ManagedOptsAlias("recipient-1-key"),
keyEncryptionAlg = "ECDH-ES+A256KW",
contentEncryptionAlg = "A256GCM"
)
)
if (prepareResult.isOk) {
val prepared = prepareResult.value
// Create multi-recipient JWE
val generalResult = jweService.createJweJsonGeneral(
CreateJweJsonGeneralArgs(
preparedJwe = prepared,
additionalRecipients = listOf(
AdditionalRecipient(
recipient = ManagedOptsAlias("recipient-2-key"),
keyEncryptionAlg = "ECDH-ES+A256KW"
),
AdditionalRecipient(
recipient = ManagedOptsAlias("recipient-3-key"),
keyEncryptionAlg = "RSA-OAEP-256"
)
)
)
)
// Result: JweJsonGeneral with recipients array
// Each recipient can decrypt using their own key
} else {
// handle error
}
// Prepare for primary recipient
let prepareResult = try await jweService.prepareJwe(
args: PrepareJweArgs(
plaintext: plaintext,
recipient: ManagedOptsAlias(identifier: "recipient-1-key"),
keyEncryptionAlg: "ECDH-ES+A256KW",
contentEncryptionAlg: "A256GCM"
)
)
if prepareResult.isOk {
let prepared = prepareResult.value
// Create multi-recipient JWE
let generalResult = try await jweService.createJweJsonGeneral(
args: CreateJweJsonGeneralArgs(
preparedJwe: prepared,
additionalRecipients: [
AdditionalRecipient(
recipient: ManagedOptsAlias(identifier: "recipient-2-key"),
keyEncryptionAlg: "ECDH-ES+A256KW"
),
AdditionalRecipient(
recipient: ManagedOptsAlias(identifier: "recipient-3-key"),
keyEncryptionAlg: "RSA-OAEP-256"
)
]
)
)
} else {
// handle error
}
Decrypting JWE
- Android/Kotlin
- iOS/Swift
// Parse the JWE (handles compact, flattened, and general formats)
val jwe = Jwe.parse(jweString)
// Decrypt using recipient's private key
val decryptResult = jweService.decryptJwe(
DecryptJweArgs(
jwe = jwe,
decryptor = ManagedOptsAlias("my-private-key")
)
)
if (decryptResult.isOk) {
val decrypted = decryptResult.value
val plaintext = decrypted.plaintext
println("Decrypted: ${plaintext.decodeToString()}")
// Access header information
val header = decrypted.header
println("Algorithm: ${header.alg}")
println("Encryption: ${header.enc}")
} else {
println("Decryption failed: ${decryptResult.error.message}")
}
// Parse the JWE (handles compact, flattened, and general formats)
let jwe = Jwe.parse(jweString: jweString)
// Decrypt using recipient's private key
let decryptResult = try await jweService.decryptJwe(
args: DecryptJweArgs(
jwe: jwe,
decryptor: ManagedOptsAlias(identifier: "my-private-key")
)
)
if decryptResult.isOk {
let decrypted = decryptResult.value
let plaintext = decrypted.plaintext
print("Decrypted: \(String(data: plaintext, encoding: .utf8)!)")
// Access header information
let header = decrypted.header
print("Algorithm: \(header.alg)")
print("Encryption: \(header.enc)")
} else {
print("Decryption failed: \(decryptResult.error.message)")
}
COSE Operations
COSE is the binary counterpart to JOSE, encoding everything in CBOR instead of JSON. The main structure you will work with is COSE_Sign1, which is a single-signer signature. This is the format ISO 18013-5 uses for mDoc issuer-signed data. If you are not working with mDoc or CBOR protocols, you probably want the JOSE/JWS section above instead.
The IDK provides CoseCryptoService for COSE operations.
Obtaining the Service
- Android/Kotlin
- iOS/Swift
val coseCryptoService = session.graph.coseCryptoService
let coseCryptoService = session.graph.coseCryptoService
Creating COSE_Sign1
COSE_Sign1 is the single-signer signature format used in mDoc:
- Android/Kotlin
- iOS/Swift
// Build COSE_Sign1 input
val coseSign1Input = CoseSign1Input(
protectedHeader = CoseHeaderCbor(
alg = SignatureAlgorithm.ECDSA_SHA256.cose
),
unprotectedHeader = null,
payload = CborByteString("Payload data".encodeToByteArray())
)
// Get key info for signing
val keyInfo = KeyInfo<CoseKeyType>(
alias = "my-signing-key",
providerId = "mobile",
keyVisibility = KeyVisibility.PRIVATE,
signatureAlgorithm = SignatureAlgorithm.ECDSA_SHA256
)
// Sign
val signResult = coseCryptoService.sign1<ByteArray>(
input = coseSign1Input,
keyInfo = keyInfo,
requireX5Chain = false
)
val coseSign1 = signResult.coseSign1
val cborBytes = coseSign1.toCbor()
// Build COSE_Sign1 input
let coseSign1Input = CoseSign1Input(
protectedHeader: CoseHeaderCbor(
alg: SignatureAlgorithm.ecdsaSha256.cose
),
unprotectedHeader: nil,
payload: CborByteString(data: "Payload data".data(using: .utf8)!)
)
// Get key info for signing
let keyInfo = KeyInfo<CoseKeyType>(
alias: "my-signing-key",
providerId: "mobile",
keyVisibility: .private_,
signatureAlgorithm: .ecdsaSha256
)
// Sign
let signResult = try await coseCryptoService.sign1(
input: coseSign1Input,
keyInfo: keyInfo,
requireX5Chain: false
)
let coseSign1 = signResult.coseSign1
let cborBytes = coseSign1.toCbor()
Verifying COSE_Sign1
- Android/Kotlin
- iOS/Swift
// Parse COSE_Sign1 from CBOR bytes
val coseSign1 = CoseSign1.fromCbor<ByteArray>(cborBytes)
// Get public key for verification
val publicKeyInfo = KeyInfo<CoseKeyType>(
alias = "issuer-public-key",
providerId = "software",
keyVisibility = KeyVisibility.PUBLIC
)
// Verify
val verificationResult = coseCryptoService.verify1(
input = coseSign1,
keyInfo = publicKeyInfo,
requireX5Chain = false
)
if (verificationResult.isValid) {
val payload = coseSign1.payload?.value
println("Verified payload: ${payload?.decodeToString()}")
} else {
println("Verification failed: ${verificationResult.error}")
}
// Parse COSE_Sign1 from CBOR bytes
let coseSign1 = CoseSign1.fromCbor(cborBytes)
// Get public key for verification
let publicKeyInfo = KeyInfo<CoseKeyType>(
alias: "issuer-public-key",
providerId: "software",
keyVisibility: .public_
)
// Verify
let verificationResult = try await coseCryptoService.verify1(
input: coseSign1,
keyInfo: publicKeyInfo,
requireX5Chain: false
)
if verificationResult.isValid {
let payload = coseSign1.payload?.value
print("Verified payload: \(String(data: payload!, encoding: .utf8)!)")
} else {
print("Verification failed: \(verificationResult.error)")
}
Key Format Conversion
In practice, you often have a key in one format but need it in the other. For example, your KMS stores keys as JWKs, but you need a COSE_Key to sign an mDoc. The IDK provides bidirectional conversion between JWK and COSE_Key that preserves key material, key IDs, and algorithm metadata.
Converting Existing Keys
Use KeyInfo or ResolvedKeyInfo to work with existing keys and convert between formats:
- Android/Kotlin
- iOS/Swift
// Start with an existing JWK
val existingJwk: Jwk = loadJwkFromSomewhere()
// Create KeyInfo for JOSE operations
val joseKeyInfo = ResolvedKeyInfo(
key = existingJwk,
kid = existingJwk.kid,
keyVisibility = KeyVisibility.PUBLIC,
signatureAlgorithm = SignatureAlgorithm.ECDSA_SHA256,
keyEncoding = KeyEncoding.JOSE
)
// Convert JWK to COSE Key
val coseKey = CoseKey.fromJwk(existingJwk)
// Create KeyInfo for COSE operations
val coseKeyInfo = ResolvedKeyInfo(
key = coseKey,
kid = coseKey.kid?.value,
keyVisibility = KeyVisibility.PUBLIC,
signatureAlgorithm = SignatureAlgorithm.ECDSA_SHA256,
keyEncoding = KeyEncoding.COSE
)
// Start with an existing JWK
let existingJwk: Jwk = loadJwkFromSomewhere()
// Create KeyInfo for JOSE operations
let joseKeyInfo = ResolvedKeyInfo(
key: existingJwk,
kid: existingJwk.kid,
keyVisibility: .public_,
signatureAlgorithm: .ecdsaSha256,
keyEncoding: .jose
)
// Convert JWK to COSE Key
let coseKey = CoseKey.fromJwk(jwk: existingJwk)
// Create KeyInfo for COSE operations
let coseKeyInfo = ResolvedKeyInfo(
key: coseKey,
kid: coseKey.kid?.value,
keyVisibility: .public_,
signatureAlgorithm: .ecdsaSha256,
keyEncoding: .cose
)
Direct Key Conversion
Convert keys directly between formats:
- Android/Kotlin
- iOS/Swift
// JWK to COSE Key
val jwk: Jwk = ...
val coseKey = CoseKey.fromJwk(jwk)
// COSE Key to JWK
val coseKey: CoseKey = ...
val jwk = coseKey.toJwk()
// Both directions preserve key material and metadata
println("Original kid: ${jwk.kid}")
println("Converted kid: ${coseKey.kid?.value}")
// JWK to COSE Key
let jwk: Jwk = ...
let coseKey = CoseKey.fromJwk(jwk: jwk)
// COSE Key to JWK
let coseKey: CoseKey = ...
let jwk = coseKey.toJwk()
// Both directions preserve key material and metadata
print("Original kid: \(jwk.kid ?? "none")")
print("Converted kid: \(coseKey.kid?.value ?? "none")")
Algorithm Mapping
COSE identifies algorithms by integer IDs (e.g., -7 for ES256) while JOSE uses string names (e.g., "ES256"). The SignatureAlgorithm enum is the IDK's unified representation, and you can convert to either format from it.
- Android/Kotlin
- iOS/Swift
// From SignatureAlgorithm to both formats
val algorithm = SignatureAlgorithm.ECDSA_SHA256
val coseAlg = algorithm.cose // CoseAlgorithm.ES256 (-7)
val joseAlg = algorithm.jose // JwaAlgorithm.ES256 ("ES256")
// Convert COSE algorithm to JOSE
val joseFromCose = SignatureAlgorithm.toJose(CoseAlgorithm.ES256)
// Result: JwaAlgorithm.ES256
// Convert JOSE algorithm to COSE
val coseFromJose = SignatureAlgorithm.toCose(JwaAlgorithm.RS256)
// Result: CoseAlgorithm.RS256
// Flexible conversion from any format
val alg = SignatureAlgorithm.toJoseAlg(-7) // Works with Int (COSE ID)
val alg2 = SignatureAlgorithm.toCoseAlg("ES256") // Works with String (JOSE name)
// From SignatureAlgorithm to both formats
let algorithm = SignatureAlgorithm.ecdsaSha256
let coseAlg = algorithm.cose // CoseAlgorithm.es256 (-7)
let joseAlg = algorithm.jose // JwaAlgorithm.es256 ("ES256")
// Convert COSE algorithm to JOSE
let joseFromCose = SignatureAlgorithm.toJose(cose: .es256)
// Result: JwaAlgorithm.es256
// Convert JOSE algorithm to COSE
let coseFromJose = SignatureAlgorithm.toCose(jose: .rs256)
// Result: CoseAlgorithm.rs256
Key Type and Curve Mapping
- Android/Kotlin
- iOS/Swift
// Key type mapping
val keyType = KeyTypeMapping.EC
val joseKty = keyType.jose // JwaKeyType.EC
val coseKty = keyType.cose // CoseKeyTypeEnum.EC2
// Convert from COSE to JOSE
val joseKeyType = KeyTypeMapping.toJose(CoseKeyTypeEnum.EC2)
// Result: JwaKeyType.EC
// Curve mapping
val curve = Curve.P_256
val joseCrv = curve.jose // JwaCurve.P_256
val coseCrv = curve.cose // CoseCurve.P_256
// Key operations mapping
val ops = KeyOperations.SIGN
val joseOps = ops.jose // JoseKeyOperations.SIGN
val coseOps = ops.cose // CoseKeyOperations.SIGN
// Key type mapping
let keyType = KeyTypeMapping.ec
let joseKty = keyType.jose // JwaKeyType.ec
let coseKty = keyType.cose // CoseKeyTypeEnum.ec2
// Convert from COSE to JOSE
let joseKeyType = KeyTypeMapping.toJose(cose: .ec2)
// Result: JwaKeyType.ec
// Curve mapping
let curve = Curve.p256
let joseCrv = curve.jose // JwaCurve.p256
let coseCrv = curve.cose // CoseCurve.p256
// Key operations mapping
let ops = KeyOperations.sign
let joseOps = ops.jose // JoseKeyOperations.sign
let coseOps = ops.cose // CoseKeyOperations.sign
Algorithm Reference
The tables below list all supported algorithms with their COSE and JOSE identifiers. For new deployments, ES256 (P-256) is the most widely supported choice across both JOSE and COSE ecosystems. Use Ed25519 if you want smaller signatures and do not need NIST curve compatibility. RSA algorithms are mainly relevant for legacy systems.
Signature Algorithms
| Algorithm | COSE ID | JOSE Name | Curve/Key |
|---|---|---|---|
ED25519 | -8 | EdDSA | Ed25519 |
ECDSA_SHA256 | -7 | ES256 | P-256 |
ECDSA_SHA384 | -35 | ES384 | P-384 |
ECDSA_SHA512 | -36 | ES512 | P-521 |
RSA_SHA256 | -257 | RS256 | RSA |
RSA_SHA384 | -258 | RS384 | RSA |
RSA_SHA512 | -259 | RS512 | RSA |
RSA_SSA_PSS_SHA256_MGF1 | -37 | PS256 | RSA |
RSA_SSA_PSS_SHA384_MGF1 | -38 | PS384 | RSA |
RSA_SSA_PSS_SHA512_MGF1 | -39 | PS512 | RSA |
Key Types
| Key Type | COSE | JOSE |
|---|---|---|
| Elliptic Curve | EC2 (2) | EC |
| RSA | RSA (3) | RSA |
| Octet Key Pair | OKP (1) | OKP |
Curves
| Curve | COSE | JOSE |
|---|---|---|
| P-256 | P-256 (1) | P-256 |
| P-384 | P-384 (2) | P-384 |
| P-521 | P-521 (3) | P-521 |
| Ed25519 | Ed25519 (6) | Ed25519 |
| X25519 | X25519 (4) | X25519 |
| secp256k1 | secp256k1 (8) | secp256k1 |
When to Use JOSE vs COSE
Use JOSE when:
- Working with OAuth 2.0 or OpenID Connect
- Implementing OID4VP or OID4VCI protocols
- Working with SD-JWT credentials
- Building web APIs
- Human readability aids debugging
Use COSE when:
- Working with ISO mDoc credentials (ISO 18013-5)
- Size constraints are critical
- Interoperating with CBOR-based systems
- Building IoT or constrained device applications
Many identity solutions use both formats. The IDK makes it straightforward to work with both and convert between them as needed.