Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,9 @@ internal object WebCryptoCryptographyProvider : CryptographyProvider() {
RSA.PSS -> WebCryptoRsaPss
RSA.PKCS1 -> WebCryptoRsaPkcs1
ECDSA -> WebCryptoEcdsa
EdDSA -> WebCryptoEdDSA
ECDH -> WebCryptoEcdh
XDH -> WebCryptoXDH
PBKDF2 -> WebCryptoPbkdf2
HKDF -> WebCryptoHkdf
else -> null
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
/*
* Copyright (c) 2025 Oleg Yukhnevich. Use of this source code is governed by the Apache 2.0 license.
*/

package dev.whyoleg.cryptography.providers.webcrypto.algorithms

import dev.whyoleg.cryptography.algorithms.*
import dev.whyoleg.cryptography.materials.key.*
import dev.whyoleg.cryptography.operations.*
import dev.whyoleg.cryptography.providers.webcrypto.internal.*
import dev.whyoleg.cryptography.providers.webcrypto.materials.*
import dev.whyoleg.cryptography.providers.webcrypto.operations.*
import dev.whyoleg.cryptography.providers.base.materials.*
import dev.whyoleg.cryptography.serialization.pem.*

internal object WebCryptoEdDSA : EdDSA {
private fun curveName(curve: EdDSA.Curve): String = when (curve) {
EdDSA.Curve.Ed25519 -> "Ed25519"
EdDSA.Curve.Ed448 -> "Ed448"
}

override fun publicKeyDecoder(curve: EdDSA.Curve): KeyDecoder<EdDSA.PublicKey.Format, EdDSA.PublicKey> = WebCryptoKeyDecoder(
algorithm = Algorithm(curveName(curve)),
keyProcessor = EdPublicKeyProcessor,
keyWrapper = WebCryptoKeyWrapper(arrayOf("verify")) { EdDsaPublicKey(it) },
)

override fun privateKeyDecoder(curve: EdDSA.Curve): KeyDecoder<EdDSA.PrivateKey.Format, EdDSA.PrivateKey> = WebCryptoKeyDecoder(
algorithm = Algorithm(curveName(curve)),
keyProcessor = EdPrivateKeyProcessor,
keyWrapper = WebCryptoKeyWrapper(arrayOf("sign")) { EdDsaPrivateKey(it) },
)

override fun keyPairGenerator(curve: EdDSA.Curve): KeyGenerator<EdDSA.KeyPair> = WebCryptoAsymmetricKeyGenerator(
algorithm = Algorithm(curveName(curve)),
keyUsages = arrayOf("verify", "sign"),
keyPairWrapper = { EdDsaKeyPair(EdDsaPublicKey(it.publicKey), EdDsaPrivateKey(it.privateKey)) },
)

private class EdDsaKeyPair(
override val publicKey: EdDSA.PublicKey,
override val privateKey: EdDSA.PrivateKey,
) : EdDSA.KeyPair

private class EdDsaPublicKey(
val publicKey: CryptoKey,
) : WebCryptoEncodableKey<EdDSA.PublicKey.Format>(publicKey, EdPublicKeyProcessor), EdDSA.PublicKey {
override fun signatureVerifier(): SignatureVerifier {
return WebCryptoSignatureVerifier(publicKey.algorithm, publicKey)
}
}

private class EdDsaPrivateKey(
val privateKey: CryptoKey,
) : WebCryptoEncodableKey<EdDSA.PrivateKey.Format>(privateKey, EdPrivateKeyProcessor), EdDSA.PrivateKey {
override fun signatureGenerator(): SignatureGenerator {
return WebCryptoSignatureGenerator(privateKey.algorithm, privateKey)
}
}
}

private object EdPublicKeyProcessor : WebCryptoKeyProcessor<EdDSA.PublicKey.Format>() {
override fun stringFormat(format: EdDSA.PublicKey.Format): String = when (format) {
EdDSA.PublicKey.Format.JWK -> "jwk"
EdDSA.PublicKey.Format.RAW -> "raw"
EdDSA.PublicKey.Format.DER,
EdDSA.PublicKey.Format.PEM -> "spki"
}

override fun beforeDecoding(algorithm: Algorithm, format: EdDSA.PublicKey.Format, key: ByteArray): ByteArray = when (format) {
EdDSA.PublicKey.Format.JWK -> key
EdDSA.PublicKey.Format.RAW -> key
EdDSA.PublicKey.Format.DER -> key
EdDSA.PublicKey.Format.PEM -> unwrapPem(PemLabel.PublicKey, key)
}

override fun afterEncoding(format: EdDSA.PublicKey.Format, key: ByteArray): ByteArray = when (format) {
EdDSA.PublicKey.Format.JWK -> key
EdDSA.PublicKey.Format.RAW -> key
EdDSA.PublicKey.Format.DER -> key
EdDSA.PublicKey.Format.PEM -> wrapPem(PemLabel.PublicKey, key)
}
}

private object EdPrivateKeyProcessor : WebCryptoKeyProcessor<EdDSA.PrivateKey.Format>() {
override fun stringFormat(format: EdDSA.PrivateKey.Format): String = when (format) {
EdDSA.PrivateKey.Format.JWK -> "jwk"
EdDSA.PrivateKey.Format.RAW -> "raw"
EdDSA.PrivateKey.Format.DER -> "pkcs8"
EdDSA.PrivateKey.Format.PEM -> "pkcs8"
}

override fun beforeDecoding(algorithm: Algorithm, format: EdDSA.PrivateKey.Format, key: ByteArray): ByteArray = when (format) {
EdDSA.PrivateKey.Format.JWK -> key
EdDSA.PrivateKey.Format.RAW -> key
EdDSA.PrivateKey.Format.DER -> key
EdDSA.PrivateKey.Format.PEM -> unwrapPem(PemLabel.PrivateKey, key)
}

override fun afterEncoding(format: EdDSA.PrivateKey.Format, key: ByteArray): ByteArray = when (format) {
EdDSA.PrivateKey.Format.JWK -> key
EdDSA.PrivateKey.Format.RAW -> key
EdDSA.PrivateKey.Format.DER -> key
EdDSA.PrivateKey.Format.PEM -> wrapPem(PemLabel.PrivateKey, key)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
/*
* Copyright (c) 2025 Oleg Yukhnevich. Use of this source code is governed by the Apache 2.0 license.
*/

package dev.whyoleg.cryptography.providers.webcrypto.algorithms

import dev.whyoleg.cryptography.algorithms.*
import dev.whyoleg.cryptography.materials.key.*
import dev.whyoleg.cryptography.operations.*
import dev.whyoleg.cryptography.providers.webcrypto.internal.*
import dev.whyoleg.cryptography.providers.webcrypto.materials.*
import dev.whyoleg.cryptography.providers.base.materials.*
import dev.whyoleg.cryptography.serialization.pem.*

internal object WebCryptoXDH : XDH {
private fun curveName(curve: XDH.Curve): String = when (curve) {
XDH.Curve.X25519 -> "X25519"
XDH.Curve.X448 -> "X448"
}

override fun publicKeyDecoder(curve: XDH.Curve): KeyDecoder<XDH.PublicKey.Format, XDH.PublicKey> = WebCryptoKeyDecoder(
algorithm = Algorithm(curveName(curve)),
keyProcessor = XdhPublicKeyProcessor,
keyWrapper = WebCryptoKeyWrapper(arrayOf()) { XdhPublicKey(it) },
)

override fun privateKeyDecoder(curve: XDH.Curve): KeyDecoder<XDH.PrivateKey.Format, XDH.PrivateKey> = WebCryptoKeyDecoder(
algorithm = Algorithm(curveName(curve)),
keyProcessor = XdhPrivateKeyProcessor,
keyWrapper = WebCryptoKeyWrapper(arrayOf("deriveBits")) { XdhPrivateKey(it) },
)

override fun keyPairGenerator(curve: XDH.Curve): KeyGenerator<XDH.KeyPair> = WebCryptoAsymmetricKeyGenerator(
algorithm = Algorithm(curveName(curve)),
keyUsages = arrayOf("deriveBits"),
keyPairWrapper = { XdhKeyPair(XdhPublicKey(it.publicKey), XdhPrivateKey(it.privateKey)) },
)

private class XdhKeyPair(
override val publicKey: XDH.PublicKey,
override val privateKey: XDH.PrivateKey,
) : XDH.KeyPair

private class XdhPublicKey(
val publicKey: CryptoKey,
) : WebCryptoEncodableKey<XDH.PublicKey.Format>(publicKey, XdhPublicKeyProcessor), XDH.PublicKey, SharedSecretGenerator<XDH.PrivateKey> {
override fun sharedSecretGenerator(): SharedSecretGenerator<XDH.PrivateKey> = this

private fun deriveLengthBits(): Int = when (publicKey.algorithm.algorithmName) {
"X25519" -> 32 * 8
"X448" -> 56 * 8
else -> error("Unknown XDH algorithm: ${publicKey.algorithm.algorithmName}")
}

override suspend fun generateSharedSecretToByteArray(other: XDH.PrivateKey): ByteArray {
check(other is XdhPrivateKey)
return WebCrypto.deriveBits(
algorithm = KeyDeriveAlgorithm(publicKey.algorithm.algorithmName, publicKey),
baseKey = other.privateKey,
length = deriveLengthBits()
)
}

override fun generateSharedSecretToByteArrayBlocking(other: XDH.PrivateKey): ByteArray = nonBlocking()
}

private class XdhPrivateKey(
val privateKey: CryptoKey,
) : WebCryptoEncodableKey<XDH.PrivateKey.Format>(privateKey, XdhPrivateKeyProcessor), XDH.PrivateKey, SharedSecretGenerator<XDH.PublicKey> {
override fun sharedSecretGenerator(): SharedSecretGenerator<XDH.PublicKey> = this

private fun deriveLengthBits(): Int = when (privateKey.algorithm.algorithmName) {
"X25519" -> 32 * 8
"X448" -> 56 * 8
else -> error("Unknown XDH algorithm: ${privateKey.algorithm.algorithmName}")
}

override suspend fun generateSharedSecretToByteArray(other: XDH.PublicKey): ByteArray {
check(other is XdhPublicKey)
return WebCrypto.deriveBits(
algorithm = KeyDeriveAlgorithm(privateKey.algorithm.algorithmName, other.publicKey),
baseKey = privateKey,
length = deriveLengthBits()
)
}

override fun generateSharedSecretToByteArrayBlocking(other: XDH.PublicKey): ByteArray = nonBlocking()
}
}

private object XdhPublicKeyProcessor : WebCryptoKeyProcessor<XDH.PublicKey.Format>() {
override fun stringFormat(format: XDH.PublicKey.Format): String = when (format) {
XDH.PublicKey.Format.JWK -> "jwk"
XDH.PublicKey.Format.RAW -> "raw"
XDH.PublicKey.Format.DER,
XDH.PublicKey.Format.PEM -> "spki"
}

override fun beforeDecoding(algorithm: Algorithm, format: XDH.PublicKey.Format, key: ByteArray): ByteArray = when (format) {
XDH.PublicKey.Format.JWK -> key
XDH.PublicKey.Format.RAW -> key
XDH.PublicKey.Format.DER -> key
XDH.PublicKey.Format.PEM -> unwrapPem(PemLabel.PublicKey, key)
}

override fun afterEncoding(format: XDH.PublicKey.Format, key: ByteArray): ByteArray = when (format) {
XDH.PublicKey.Format.JWK -> key
XDH.PublicKey.Format.RAW -> key
XDH.PublicKey.Format.DER -> key
XDH.PublicKey.Format.PEM -> wrapPem(PemLabel.PublicKey, key)
}
}

private object XdhPrivateKeyProcessor : WebCryptoKeyProcessor<XDH.PrivateKey.Format>() {
override fun stringFormat(format: XDH.PrivateKey.Format): String = when (format) {
XDH.PrivateKey.Format.JWK -> "jwk"
XDH.PrivateKey.Format.RAW -> "raw"
XDH.PrivateKey.Format.DER -> "pkcs8"
XDH.PrivateKey.Format.PEM -> "pkcs8"
}

override fun beforeDecoding(algorithm: Algorithm, format: XDH.PrivateKey.Format, key: ByteArray): ByteArray = when (format) {
XDH.PrivateKey.Format.JWK -> key
XDH.PrivateKey.Format.RAW -> key
XDH.PrivateKey.Format.DER -> key
XDH.PrivateKey.Format.PEM -> unwrapPem(PemLabel.PrivateKey, key)
}

override fun afterEncoding(format: XDH.PrivateKey.Format, key: ByteArray): ByteArray = when (format) {
XDH.PrivateKey.Format.JWK -> key
XDH.PrivateKey.Format.RAW -> key
XDH.PrivateKey.Format.DER -> key
XDH.PrivateKey.Format.PEM -> wrapPem(PemLabel.PrivateKey, key)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ internal expect val Algorithm.ecKeyAlgorithmNamedCurve: String

internal expect fun EcdsaSignatureAlgorithm(hash: String): Algorithm

internal expect fun EcdhKeyDeriveAlgorithm(publicKey: CryptoKey): Algorithm
internal expect fun KeyDeriveAlgorithm(name: String, publicKey: CryptoKey): Algorithm

internal expect fun Pbkdf2DeriveAlgorithm(hash: String, iterations: Int, salt: ByteArray): Algorithm

Expand All @@ -53,3 +53,4 @@ internal expect fun RsaOaepCipherAlgorithm(label: ByteArray?): Algorithm
internal expect fun RsaPssSignatureAlgorithm(saltLength: Int): Algorithm

internal expect val Algorithm.rsaKeyAlgorithmHashName: String
internal expect val Algorithm.algorithmName: String
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,8 @@ private fun ecKeyAlgorithmNamedCurve(algorithm: Algorithm): String = js("algorit
internal actual fun EcdsaSignatureAlgorithm(hash: String): Algorithm =
js("{ name: 'ECDSA', hash: hash }").unsafeCast<Algorithm>()

internal actual fun EcdhKeyDeriveAlgorithm(publicKey: CryptoKey): Algorithm =
js("{ name: 'ECDH', public: publicKey }").unsafeCast<Algorithm>()
internal actual fun KeyDeriveAlgorithm(name: String, publicKey: CryptoKey): Algorithm =
js("{ name: name, public: publicKey }").unsafeCast<Algorithm>()

internal actual fun Pbkdf2DeriveAlgorithm(hash: String, iterations: Int, salt: ByteArray): Algorithm =
js("{ name: 'PBKDF2', hash: hash, iterations: iterations, salt: salt }").unsafeCast<Algorithm>()
Expand Down Expand Up @@ -77,3 +77,8 @@ internal actual val Algorithm.rsaKeyAlgorithmHashName: String get() = rsaKeyAlgo

@Suppress("UNUSED_PARAMETER")
private fun rsaKeyAlgorithmHashName(algorithm: Algorithm): String = js("algorithm.hash.name").unsafeCast<String>()

internal actual val Algorithm.algorithmName: String get() = algorithmName(this)

@Suppress("UNUSED_PARAMETER")
private fun algorithmName(algorithm: Algorithm): String = js("algorithm.name").unsafeCast<String>()
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,8 @@ private fun ecKeyAlgorithmNamedCurve(algorithm: Algorithm): String = js("algorit
internal actual fun EcdsaSignatureAlgorithm(hash: String): Algorithm =
js("({ name: 'ECDSA', hash: hash })")

internal actual fun EcdhKeyDeriveAlgorithm(publicKey: CryptoKey): Algorithm =
js("({ name: 'ECDH', public: publicKey })")
internal actual fun KeyDeriveAlgorithm(name: String, publicKey: CryptoKey): Algorithm =
js("({ name: name, public: publicKey })")

internal actual fun Pbkdf2DeriveAlgorithm(hash: String, iterations: Int, salt: ByteArray): Algorithm =
jsPbkdf2DeriveAlgorithm(hash, iterations, salt.toInt8Array())
Expand Down Expand Up @@ -105,3 +105,8 @@ internal actual val Algorithm.rsaKeyAlgorithmHashName: String get() = rsaKeyAlgo

@Suppress("UNUSED_PARAMETER")
private fun rsaKeyAlgorithmHashName(algorithm: Algorithm): String = js("algorithm.hash.name")

internal actual val Algorithm.algorithmName: String get() = algorithmName(this)

@Suppress("UNUSED_PARAMETER")
private fun algorithmName(algorithm: Algorithm): String = js("algorithm.name")
4 changes: 4 additions & 0 deletions docs/providers/webcrypto.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ For supported targets and algorithms, please consult [Supported primitives secti
* only `suspend` functions are supported, because `WebCrypto` API is async by default
* AES.* (browser only): may not support `192 bit` keys
* AES.CBC: only `padding=true` is supported
* EdDSA/XDH were added later to WebCrypto and might not be available in all browsers (https://github.com/w3c/webcrypto/pull/362)

## Example

Expand All @@ -32,3 +33,6 @@ dependencies {
[WebCrypto]: https://developer.mozilla.org/en-US/docs/Web/API/Web_Crypto_API

[Supported primitives section]: index.md#supported-primitives

[RFC 8032]: https://www.rfc-editor.org/rfc/rfc8032
[RFC 7748]: https://www.rfc-editor.org/rfc/rfc7748