use std::borrow::Cow;
use base64::Engine;
use base64::prelude::BASE64_URL_SAFE_NO_PAD;
use deno_core::ToV8;
use deno_core::v8;
use deno_core::webidl::ContextFn;
use deno_core::webidl::WebIdlConverter;
use deno_core::webidl::WebIdlError;
use deno_core::webidl::WebIdlErrorKind;
use deno_error::JsErrorBox;
use crate::CryptoError;
use crate::crypto_key::CryptoKeyType;
use crate::ed25519::export_pkcs8_ed25519;
use crate::ed25519::export_spki_ed25519;
use crate::ed25519::jwk_x_ed25519;
use crate::mldsa::mldsa_export_pkcs8;
use crate::mldsa::mldsa_export_spki;
use crate::mldsa::mldsa_from_seed;
use crate::mlkem::MlKemVariant;
use crate::mlkem::ml_kem_export_pkcs8;
use crate::mlkem::ml_kem_export_spki;
use crate::shared::ShaHash;
use crate::subtle_key::SubtleKey;
use crate::x448::export_pkcs8_x448;
use crate::x448::export_spki_x448;
use crate::x448::x448_public_key;
use crate::x25519::export_pkcs8_x25519;
use crate::x25519::export_spki_x25519;
use crate::x25519::x25519_public_key;
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
pub enum KeyFormat {
Raw,
RawSecret,
RawPublic,
RawPrivate,
RawSeed,
Spki,
Pkcs8,
Jwk,
}
impl<'a> WebIdlConverter<'a> for KeyFormat {
type Options = ();
fn convert<'b>(
scope: &mut v8::PinScope<'a, '_>,
value: v8::Local<'a, v8::Value>,
prefix: Cow<'static, str>,
context: ContextFn<'b>,
_options: &Self::Options,
) -> Result<Self, WebIdlError> {
let Some(s) = value.to_string(scope) else {
return Err(WebIdlError::new(
prefix,
context,
WebIdlErrorKind::ConvertToConverterType("KeyFormat"),
));
};
let s = s.to_rust_string_lossy(scope);
Ok(match s.as_str() {
"raw" => Self::Raw,
"raw-secret" => Self::RawSecret,
"raw-public" => Self::RawPublic,
"raw-private" => Self::RawPrivate,
"raw-seed" => Self::RawSeed,
"spki" => Self::Spki,
"pkcs8" => Self::Pkcs8,
"jwk" => Self::Jwk,
_ => {
return Err(WebIdlError::other(
prefix,
context,
JsErrorBox::type_error(format!(
"The provided value '{s}' is not a valid enum value of type KeyFormat"
)),
));
}
})
}
}
pub enum ExportKeyOutput {
Bytes(Vec<u8>),
Jwk(Box<JsonWebKey>),
}
#[derive(Default)]
pub struct JsonWebKey {
pub kty: &'static str,
pub alg: Option<String>,
pub crv: Option<String>,
pub k: Option<String>,
pub n: Option<String>,
pub e: Option<String>,
pub d: Option<String>,
pub p: Option<String>,
pub q: Option<String>,
pub dp: Option<String>,
pub dq: Option<String>,
pub qi: Option<String>,
pub x: Option<String>,
pub y: Option<String>,
pub pub_field: Option<String>,
pub priv_field: Option<String>,
pub key_ops: Vec<String>,
pub ext: bool,
}
impl<'a> ToV8<'a> for ExportKeyOutput {
type Error = JsErrorBox;
fn to_v8(
self,
scope: &mut v8::PinScope<'a, '_>,
) -> Result<v8::Local<'a, v8::Value>, Self::Error> {
match self {
ExportKeyOutput::Bytes(bytes) => Ok(bytes_to_array_buffer(scope, bytes)),
ExportKeyOutput::Jwk(jwk) => Ok(jwk_to_object(scope, *jwk).into()),
}
}
}
fn bytes_to_array_buffer<'s>(
scope: &mut v8::PinScope<'s, '_>,
bytes: Vec<u8>,
) -> v8::Local<'s, v8::Value> {
if bytes.is_empty() {
return v8::ArrayBuffer::new(scope, 0).into();
}
let backing =
v8::ArrayBuffer::new_backing_store_from_bytes(bytes.into_boxed_slice());
let shared = backing.make_shared();
v8::ArrayBuffer::with_backing_store(scope, &shared).into()
}
fn jwk_to_object<'s>(
scope: &mut v8::PinScope<'s, '_>,
jwk: JsonWebKey,
) -> v8::Local<'s, v8::Object> {
let obj = v8::Object::new(scope);
set_str(scope, obj, b"kty", jwk.kty);
if let Some(v) = jwk.alg.as_deref() {
set_str(scope, obj, b"alg", v);
}
if let Some(v) = jwk.crv.as_deref() {
set_str(scope, obj, b"crv", v);
}
if let Some(v) = jwk.k.as_deref() {
set_str(scope, obj, b"k", v);
}
if let Some(v) = jwk.n.as_deref() {
set_str(scope, obj, b"n", v);
}
if let Some(v) = jwk.e.as_deref() {
set_str(scope, obj, b"e", v);
}
if let Some(v) = jwk.d.as_deref() {
set_str(scope, obj, b"d", v);
}
if let Some(v) = jwk.p.as_deref() {
set_str(scope, obj, b"p", v);
}
if let Some(v) = jwk.q.as_deref() {
set_str(scope, obj, b"q", v);
}
if let Some(v) = jwk.dp.as_deref() {
set_str(scope, obj, b"dp", v);
}
if let Some(v) = jwk.dq.as_deref() {
set_str(scope, obj, b"dq", v);
}
if let Some(v) = jwk.qi.as_deref() {
set_str(scope, obj, b"qi", v);
}
if let Some(v) = jwk.x.as_deref() {
set_str(scope, obj, b"x", v);
}
if let Some(v) = jwk.y.as_deref() {
set_str(scope, obj, b"y", v);
}
if let Some(v) = jwk.pub_field.as_deref() {
set_str(scope, obj, b"pub", v);
}
if let Some(v) = jwk.priv_field.as_deref() {
set_str(scope, obj, b"priv", v);
}
let arr = v8::Array::new(scope, jwk.key_ops.len() as i32);
for (i, op) in jwk.key_ops.iter().enumerate() {
let s = v8::String::new(scope, op).unwrap();
arr.set_index(scope, i as u32, s.into());
}
let key_ops_key = key_intern(scope, b"key_ops");
obj.set(scope, key_ops_key.into(), arr.into());
let ext_key = key_intern(scope, b"ext");
let ext_val = v8::Boolean::new(scope, jwk.ext);
obj.set(scope, ext_key.into(), ext_val.into());
obj
}
fn set_str<'s>(
scope: &mut v8::PinScope<'s, '_>,
obj: v8::Local<'s, v8::Object>,
field: &[u8],
value: &str,
) {
let key = key_intern(scope, field);
let v = v8::String::new(scope, value).unwrap();
obj.set(scope, key.into(), v.into());
}
fn key_intern<'s>(
scope: &mut v8::PinScope<'s, '_>,
bytes: &[u8],
) -> v8::Local<'s, v8::String> {
v8::String::new_from_one_byte(scope, bytes, v8::NewStringType::Internalized)
.unwrap()
}
fn b64_url(bytes: &[u8]) -> String {
BASE64_URL_SAFE_NO_PAD.encode(bytes)
}
fn invalid_access(msg: &'static str) -> CryptoError {
CryptoError::Other(JsErrorBox::new("DOMExceptionInvalidAccessError", msg))
}
fn not_supported(msg: String) -> CryptoError {
CryptoError::Other(JsErrorBox::new("DOMExceptionNotSupportedError", msg))
}
fn op_error(msg: String) -> CryptoError {
CryptoError::Other(JsErrorBox::new("DOMExceptionOperationError", msg))
}
pub fn run(
format: KeyFormat,
key: SubtleKey,
) -> Result<ExportKeyOutput, CryptoError> {
if !key.extractable {
return Err(invalid_access("Key is not extractable"));
}
match key.algorithm_name.as_str() {
"HMAC" => export_symmetric(format, &key, SymKind::Hmac),
"KMAC128" | "KMAC256" => export_symmetric(format, &key, SymKind::Kmac),
"AES-CTR" | "AES-CBC" | "AES-GCM" | "AES-OCB" | "AES-KW" => {
export_symmetric(format, &key, SymKind::Aes)
}
"ChaCha20-Poly1305" => export_symmetric(format, &key, SymKind::ChaCha),
"RSASSA-PKCS1-v1_5" | "RSA-PSS" | "RSA-OAEP" => export_rsa(format, &key),
"ECDSA" | "ECDH" => export_ec(format, &key),
"Ed25519" => export_okp(format, &key, OkpKind::Ed25519),
"X25519" => export_okp(format, &key, OkpKind::X25519),
"X448" => export_okp(format, &key, OkpKind::X448),
"ML-KEM-512" | "ML-KEM-768" | "ML-KEM-1024" => {
export_akp_mlkem(format, &key)
}
"ML-DSA-44" | "ML-DSA-65" | "ML-DSA-87" => export_akp_mldsa(format, &key),
_ if crate::slhdsa::variant_from_name(&key.algorithm_name).is_some() => {
export_akp_slh_dsa(format, &key)
}
other => Err(not_supported(format!(
"Unrecognized algorithm name: {other}"
))),
}
}
#[derive(Copy, Clone)]
enum SymKind {
Aes,
Hmac,
Kmac,
ChaCha,
}
fn export_symmetric(
format: KeyFormat,
key: &SubtleKey,
kind: SymKind,
) -> Result<ExportKeyOutput, CryptoError> {
let raw_alias_allowed = !matches!(kind, SymKind::ChaCha);
match format {
KeyFormat::RawSecret => {
Ok(ExportKeyOutput::Bytes(key.raw.bytes().to_vec()))
}
KeyFormat::Raw if raw_alias_allowed => {
Ok(ExportKeyOutput::Bytes(key.raw.bytes().to_vec()))
}
KeyFormat::Jwk => {
let mut jwk = JsonWebKey {
kty: "oct",
ext: key.extractable,
key_ops: key.usages.clone(),
..Default::default()
};
jwk.k = Some(b64_url(key.raw.bytes()));
jwk.alg = Some(jwk_alg_for_symmetric(kind, key)?);
Ok(ExportKeyOutput::Jwk(Box::new(jwk)))
}
_ => Err(not_supported("Not implemented".to_string())),
}
}
fn jwk_alg_for_symmetric(
kind: SymKind,
key: &SubtleKey,
) -> Result<String, CryptoError> {
match kind {
SymKind::Aes => {
let len = key.algorithm_length.unwrap_or(0);
if !matches!(len, 128 | 192 | 256) {
return Err(not_supported(format!("Invalid key length: {len}")));
}
let suffix = match key.algorithm_name.as_str() {
"AES-CTR" => "CTR",
"AES-CBC" => "CBC",
"AES-GCM" => "GCM",
"AES-KW" => "KW",
"AES-OCB" => "OCB",
other => {
return Err(not_supported(format!(
"Unsupported AES variant: {other}"
)));
}
};
Ok(format!("A{len}{suffix}"))
}
SymKind::ChaCha => Ok("C20P".to_string()),
SymKind::Hmac => {
let hash = key.algorithm_hash.ok_or_else(|| {
not_supported("Hash algorithm not supported".to_string())
})?;
Ok(
match hash {
ShaHash::Sha1 => "HS1",
ShaHash::Sha256 => "HS256",
ShaHash::Sha384 => "HS384",
ShaHash::Sha512 => "HS512",
ShaHash::Sha3_256 => "HS3-256",
ShaHash::Sha3_384 => "HS3-384",
ShaHash::Sha3_512 => "HS3-512",
}
.to_string(),
)
}
SymKind::Kmac => match key.algorithm_name.as_str() {
"KMAC128" => Ok("K128".to_string()),
"KMAC256" => Ok("K256".to_string()),
other => Err(not_supported(format!("Unsupported KMAC variant: {other}"))),
},
}
}
fn export_rsa(
format: KeyFormat,
key: &SubtleKey,
) -> Result<ExportKeyOutput, CryptoError> {
use crate::export_key::ExportKeyFormat as F;
use crate::export_key::ExportKeyResult;
let raw = &key.raw;
match format {
KeyFormat::Pkcs8 => {
if key.key_type != CryptoKeyType::Private {
return Err(invalid_access("Key is not a private key"));
}
let res = crate::export_key::export_key_rsa_for_subtle(F::Pkcs8, raw)
.map_err(|e| op_error(e.to_string()))?;
match res {
ExportKeyResult::Pkcs8(b) => Ok(ExportKeyOutput::Bytes(b.0.into())),
_ => Err(op_error("unexpected".into())),
}
}
KeyFormat::Spki => {
if key.key_type != CryptoKeyType::Public {
return Err(invalid_access("Key is not a public key"));
}
let res = crate::export_key::export_key_rsa_for_subtle(F::Spki, raw)
.map_err(|e| op_error(e.to_string()))?;
match res {
ExportKeyResult::Spki(b) => Ok(ExportKeyOutput::Bytes(b.0.into())),
_ => Err(op_error("unexpected".into())),
}
}
KeyFormat::Jwk => {
let inner_format = match key.key_type {
CryptoKeyType::Public => F::JwkPublic,
CryptoKeyType::Private => F::JwkPrivate,
_ => return Err(not_supported("Not implemented".to_string())),
};
let res = crate::export_key::export_key_rsa_for_subtle(inner_format, raw)
.map_err(|e| op_error(e.to_string()))?;
let alg = rsa_jwk_alg(key)?;
let mut jwk = JsonWebKey {
kty: "RSA",
ext: key.extractable,
key_ops: key.usages.clone(),
alg: Some(alg),
..Default::default()
};
match res {
ExportKeyResult::JwkPublicRsa { n, e } => {
jwk.n = Some(n);
jwk.e = Some(e);
}
ExportKeyResult::JwkPrivateRsa {
n,
e,
d,
p,
q,
dp,
dq,
qi,
} => {
jwk.n = Some(n);
jwk.e = Some(e);
jwk.d = Some(d);
jwk.p = Some(p);
jwk.q = Some(q);
jwk.dp = Some(dp);
jwk.dq = Some(dq);
jwk.qi = Some(qi);
}
_ => return Err(op_error("unexpected".into())),
}
Ok(ExportKeyOutput::Jwk(Box::new(jwk)))
}
_ => Err(not_supported("Not implemented".to_string())),
}
}
fn rsa_jwk_alg(key: &SubtleKey) -> Result<String, CryptoError> {
let hash = key
.algorithm_hash
.ok_or_else(|| not_supported("Hash algorithm not supported".to_string()))?;
Ok(
match key.algorithm_name.as_str() {
"RSASSA-PKCS1-v1_5" => match hash {
ShaHash::Sha1 => "RS1",
ShaHash::Sha256 => "RS256",
ShaHash::Sha384 => "RS384",
ShaHash::Sha512 => "RS512",
ShaHash::Sha3_256 => "RS3-256",
ShaHash::Sha3_384 => "RS3-384",
ShaHash::Sha3_512 => "RS3-512",
},
"RSA-PSS" => match hash {
ShaHash::Sha1 => "PS1",
ShaHash::Sha256 => "PS256",
ShaHash::Sha384 => "PS384",
ShaHash::Sha512 => "PS512",
ShaHash::Sha3_256 => "PS3-256",
ShaHash::Sha3_384 => "PS3-384",
ShaHash::Sha3_512 => "PS3-512",
},
"RSA-OAEP" => match hash {
ShaHash::Sha1 => "RSA-OAEP",
ShaHash::Sha256 => "RSA-OAEP-256",
ShaHash::Sha384 => "RSA-OAEP-384",
ShaHash::Sha512 => "RSA-OAEP-512",
ShaHash::Sha3_256 => "RSA-OAEP3-256",
ShaHash::Sha3_384 => "RSA-OAEP3-384",
ShaHash::Sha3_512 => "RSA-OAEP3-512",
},
other => {
return Err(not_supported(format!("Unsupported RSA variant: {other}")));
}
}
.to_string(),
)
}
fn ec_named_curve(name: &str) -> Option<crate::shared::EcNamedCurve> {
match name {
"P-256" => Some(crate::shared::EcNamedCurve::P256),
"P-384" => Some(crate::shared::EcNamedCurve::P384),
"P-521" => Some(crate::shared::EcNamedCurve::P521),
_ => None,
}
}
fn export_ec(
format: KeyFormat,
key: &SubtleKey,
) -> Result<ExportKeyOutput, CryptoError> {
use crate::export_key::ExportKeyAlgorithm;
use crate::export_key::ExportKeyFormat as F;
use crate::export_key::ExportKeyResult;
let curve_name = key
.algorithm_named_curve
.as_deref()
.ok_or_else(|| not_supported("Missing namedCurve".to_string()))?;
let curve = ec_named_curve(curve_name)
.ok_or_else(|| not_supported(format!("Unsupported curve: {curve_name}")))?;
let algorithm = match key.algorithm_name.as_str() {
"ECDSA" => ExportKeyAlgorithm::Ecdsa { named_curve: curve },
"ECDH" => ExportKeyAlgorithm::Ecdh { named_curve: curve },
other => return Err(not_supported(format!("Unsupported EC: {other}"))),
};
let (inner_format, want_jwk) = match (format, key.key_type) {
(KeyFormat::Raw | KeyFormat::RawPublic, CryptoKeyType::Public) => {
(F::Raw, false)
}
(KeyFormat::Raw | KeyFormat::RawPublic, _) => {
return Err(invalid_access("Key is not a public key"));
}
(KeyFormat::Pkcs8, CryptoKeyType::Private) => (F::Pkcs8, false),
(KeyFormat::Pkcs8, _) => {
return Err(invalid_access("Key is not a private key"));
}
(KeyFormat::Spki, CryptoKeyType::Public) => (F::Spki, false),
(KeyFormat::Spki, _) => {
return Err(invalid_access("Key is not a public key"));
}
(KeyFormat::Jwk, CryptoKeyType::Private) => (F::JwkPrivate, true),
(KeyFormat::Jwk, CryptoKeyType::Public) => (F::JwkPublic, true),
(KeyFormat::Jwk, _) => {
return Err(not_supported("Not implemented".to_string()));
}
_ => return Err(not_supported("Not implemented".to_string())),
};
let res = crate::export_key::export_key_ec_for_subtle(
inner_format,
&key.raw,
algorithm,
curve,
)
.map_err(|e| op_error(e.to_string()))?;
if !want_jwk {
return match res {
ExportKeyResult::Raw(b)
| ExportKeyResult::Spki(b)
| ExportKeyResult::Pkcs8(b) => Ok(ExportKeyOutput::Bytes(b.0.into())),
_ => Err(op_error("unexpected".into())),
};
}
let mut jwk = JsonWebKey {
kty: "EC",
ext: key.extractable,
key_ops: key.usages.clone(),
crv: Some(curve_name.to_string()),
..Default::default()
};
jwk.alg = Some(if key.algorithm_name == "ECDSA" {
match curve {
crate::shared::EcNamedCurve::P256 => "ES256",
crate::shared::EcNamedCurve::P384 => "ES384",
crate::shared::EcNamedCurve::P521 => "ES512",
}
.to_string()
} else {
"ECDH".to_string()
});
match res {
ExportKeyResult::JwkPublicEc { x, y } => {
jwk.x = Some(x);
jwk.y = Some(y);
}
ExportKeyResult::JwkPrivateEc { x, y, d } => {
jwk.x = Some(x);
jwk.y = Some(y);
jwk.d = Some(d);
}
_ => return Err(op_error("unexpected".into())),
}
Ok(ExportKeyOutput::Jwk(Box::new(jwk)))
}
#[derive(Copy, Clone)]
enum OkpKind {
Ed25519,
X25519,
X448,
}
fn okp_crv(kind: OkpKind) -> &'static str {
match kind {
OkpKind::Ed25519 => "Ed25519",
OkpKind::X25519 => "X25519",
OkpKind::X448 => "X448",
}
}
fn export_okp(
format: KeyFormat,
key: &SubtleKey,
kind: OkpKind,
) -> Result<ExportKeyOutput, CryptoError> {
let raw = key.raw.bytes();
match format {
KeyFormat::Raw | KeyFormat::RawPublic => {
if key.key_type != CryptoKeyType::Public {
return Err(invalid_access("Key is not a public key"));
}
Ok(ExportKeyOutput::Bytes(raw.to_vec()))
}
KeyFormat::Spki => {
if key.key_type != CryptoKeyType::Public {
return Err(invalid_access("Key is not a public key"));
}
let bytes: Vec<u8> = match kind {
OkpKind::Ed25519 => {
export_spki_ed25519(raw).map_err(|e| op_error(e.to_string()))?
}
OkpKind::X25519 => {
export_spki_x25519(raw).map_err(|e| op_error(e.to_string()))?
}
OkpKind::X448 => {
export_spki_x448(raw).map_err(|e| op_error(e.to_string()))?
}
};
Ok(ExportKeyOutput::Bytes(bytes))
}
KeyFormat::Pkcs8 => {
if key.key_type != CryptoKeyType::Private {
return Err(invalid_access("Key is not a private key"));
}
let (prefix, len_byte): (&[u8], u8) = match kind {
OkpKind::Ed25519 => (&[0x04, 0x22], 0x20),
OkpKind::X25519 => (&[0x04, 0x22], 0x20),
OkpKind::X448 => (&[0x04, 0x3a], 0x38),
};
let mut combined = Vec::with_capacity(prefix.len() + raw.len());
combined.extend_from_slice(prefix);
combined.extend_from_slice(raw);
let mut pkcs8: Vec<u8> = match kind {
OkpKind::Ed25519 => export_pkcs8_ed25519(&combined)
.map_err(|e| op_error(e.to_string()))?,
OkpKind::X25519 => {
export_pkcs8_x25519(&combined).map_err(|e| op_error(e.to_string()))?
}
OkpKind::X448 => {
export_pkcs8_x448(&combined).map_err(|e| op_error(e.to_string()))?
}
};
if pkcs8.len() > 15 {
pkcs8[15] = len_byte;
}
Ok(ExportKeyOutput::Bytes(pkcs8))
}
KeyFormat::Jwk => {
let mut jwk = JsonWebKey {
kty: "OKP",
ext: key.extractable,
key_ops: key.usages.clone(),
crv: Some(okp_crv(kind).to_string()),
..Default::default()
};
if key.key_type == CryptoKeyType::Private {
let x = match kind {
OkpKind::Ed25519 => {
jwk_x_ed25519(raw).map_err(|e| op_error(e.to_string()))?
}
OkpKind::X25519 => x25519_public_key(raw),
OkpKind::X448 => {
x448_public_key(raw).map_err(|e| op_error(e.to_string()))?
}
};
jwk.x = Some(x);
jwk.d = Some(b64_url(raw));
} else {
jwk.x = Some(b64_url(raw));
}
Ok(ExportKeyOutput::Jwk(Box::new(jwk)))
}
_ => Err(not_supported("Not implemented".to_string())),
}
}
fn mlkem_variant(name: &str) -> Option<MlKemVariant> {
match name {
"ML-KEM-512" => Some(MlKemVariant::MlKem512),
"ML-KEM-768" => Some(MlKemVariant::MlKem768),
"ML-KEM-1024" => Some(MlKemVariant::MlKem1024),
_ => None,
}
}
fn mldsa_variant_id(name: &str) -> Option<u8> {
match name {
"ML-DSA-44" => Some(0),
"ML-DSA-65" => Some(1),
"ML-DSA-87" => Some(2),
_ => None,
}
}
fn export_akp_mlkem(
format: KeyFormat,
key: &SubtleKey,
) -> Result<ExportKeyOutput, CryptoError> {
let variant = mlkem_variant(&key.algorithm_name)
.ok_or_else(|| op_error("Unknown ML-KEM variant".into()))?;
match format {
KeyFormat::RawPublic => {
if key.key_type != CryptoKeyType::Public {
return Err(invalid_access(
"'raw-public' is only valid for public keys",
));
}
Ok(ExportKeyOutput::Bytes(key.raw.bytes().to_vec()))
}
KeyFormat::RawSeed => {
if key.key_type != CryptoKeyType::Private {
return Err(invalid_access(
"'raw-seed' is only valid for private keys",
));
}
let seed = key
.raw
.seed()
.ok_or_else(|| op_error("Seed is not available for this key".into()))?;
Ok(ExportKeyOutput::Bytes(seed.to_vec()))
}
KeyFormat::Spki => {
if key.key_type != CryptoKeyType::Public {
return Err(invalid_access("'spki' is only valid for public keys"));
}
let bytes: Vec<u8> = ml_kem_export_spki(variant, key.raw.bytes())
.map_err(|e| op_error(e.to_string()))?;
Ok(ExportKeyOutput::Bytes(bytes))
}
KeyFormat::Pkcs8 => {
if key.key_type != CryptoKeyType::Private {
return Err(invalid_access("'pkcs8' is only valid for private keys"));
}
let seed = key.raw.seed().ok_or_else(|| {
op_error(
"PKCS#8 export requires the original seed; this key was imported without one"
.into(),
)
})?;
let bytes: Vec<u8> = ml_kem_export_pkcs8(variant, seed)
.map_err(|e| op_error(e.to_string()))?;
Ok(ExportKeyOutput::Bytes(bytes))
}
KeyFormat::Jwk => {
let mut jwk = JsonWebKey {
kty: "AKP",
alg: Some(key.algorithm_name.clone()),
ext: key.extractable,
key_ops: key.usages.clone(),
..Default::default()
};
if key.key_type == CryptoKeyType::Private {
let seed = key.raw.seed().ok_or_else(|| {
op_error(
"JWK export requires the original seed; this key was imported without one"
.into(),
)
})?;
let pub_bytes = variant
.public_from_expanded_for_subtle(key.raw.expanded_private_key())
.map_err(|e| op_error(e.to_string()))?;
jwk.pub_field = Some(b64_url(&pub_bytes));
jwk.priv_field = Some(b64_url(seed));
} else {
jwk.pub_field = Some(b64_url(key.raw.bytes()));
}
Ok(ExportKeyOutput::Jwk(Box::new(jwk)))
}
_ => Err(not_supported(format!(
"Unsupported key format for ML-KEM: {format:?}"
))),
}
}
fn export_akp_mldsa(
format: KeyFormat,
key: &SubtleKey,
) -> Result<ExportKeyOutput, CryptoError> {
let variant = mldsa_variant_id(&key.algorithm_name)
.ok_or_else(|| op_error("Unknown ML-DSA variant".into()))?;
match format {
KeyFormat::RawPublic => {
if key.key_type != CryptoKeyType::Public {
return Err(invalid_access(
"'raw-public' is only valid for public keys",
));
}
Ok(ExportKeyOutput::Bytes(key.raw.bytes().to_vec()))
}
KeyFormat::RawSeed => {
if key.key_type != CryptoKeyType::Private {
return Err(invalid_access(
"'raw-seed' is only valid for private keys",
));
}
let seed = key
.raw
.seed()
.ok_or_else(|| op_error("Seed is not available for this key".into()))?;
Ok(ExportKeyOutput::Bytes(seed.to_vec()))
}
KeyFormat::Spki => {
if key.key_type != CryptoKeyType::Public {
return Err(invalid_access("'spki' is only valid for public keys"));
}
let bytes: Vec<u8> = mldsa_export_spki(variant, key.raw.bytes())
.map_err(|e| op_error(e.to_string()))?;
Ok(ExportKeyOutput::Bytes(bytes))
}
KeyFormat::Pkcs8 => {
if key.key_type != CryptoKeyType::Private {
return Err(invalid_access("'pkcs8' is only valid for private keys"));
}
let seed = key.raw.seed().ok_or_else(|| {
op_error(
"PKCS#8 export requires the original seed; this key was imported without one"
.into(),
)
})?;
let bytes: Vec<u8> = mldsa_export_pkcs8(variant, seed)
.map_err(|e| op_error(e.to_string()))?;
Ok(ExportKeyOutput::Bytes(bytes))
}
KeyFormat::Jwk => {
let mut jwk = JsonWebKey {
kty: "AKP",
alg: Some(key.algorithm_name.clone()),
ext: key.extractable,
key_ops: key.usages.clone(),
..Default::default()
};
if key.key_type == CryptoKeyType::Private {
let seed = key.raw.seed().ok_or_else(|| {
op_error(
"JWK export requires the original seed; this key was imported without one"
.into(),
)
})?;
let (_, pub_bytes) = mldsa_from_seed(variant, seed)
.map_err(|e| op_error(e.to_string()))?;
jwk.pub_field = Some(b64_url(&pub_bytes));
jwk.priv_field = Some(b64_url(seed));
} else {
jwk.pub_field = Some(b64_url(key.raw.bytes()));
}
Ok(ExportKeyOutput::Jwk(Box::new(jwk)))
}
_ => Err(not_supported(format!(
"Unsupported key format for ML-DSA: {format:?}"
))),
}
}
fn export_akp_slh_dsa(
format: KeyFormat,
key: &SubtleKey,
) -> Result<ExportKeyOutput, CryptoError> {
let variant = crate::slhdsa::variant_from_name(&key.algorithm_name)
.ok_or_else(|| op_error("Unknown SLH-DSA variant".into()))?;
match format {
KeyFormat::RawPublic => {
if key.key_type != CryptoKeyType::Public {
return Err(invalid_access(
"'raw-public' is only valid for public keys",
));
}
Ok(ExportKeyOutput::Bytes(key.raw.bytes().to_vec()))
}
KeyFormat::RawPrivate => {
if key.key_type != CryptoKeyType::Private {
return Err(invalid_access(
"'raw-private' is only valid for private keys",
));
}
Ok(ExportKeyOutput::Bytes(
key.raw.expanded_private_key().to_vec(),
))
}
KeyFormat::Spki => {
if key.key_type != CryptoKeyType::Public {
return Err(invalid_access("'spki' is only valid for public keys"));
}
let bytes = crate::slhdsa::export_spki(variant, key.raw.bytes())
.map_err(|e| op_error(e.to_string()))?;
Ok(ExportKeyOutput::Bytes(bytes))
}
KeyFormat::Pkcs8 => {
if key.key_type != CryptoKeyType::Private {
return Err(invalid_access("'pkcs8' is only valid for private keys"));
}
let bytes =
crate::slhdsa::export_pkcs8(variant, key.raw.expanded_private_key())
.map_err(|e| op_error(e.to_string()))?;
Ok(ExportKeyOutput::Bytes(bytes))
}
KeyFormat::Jwk => {
let mut jwk = JsonWebKey {
kty: "AKP",
alg: Some(key.algorithm_name.clone()),
ext: key.extractable,
key_ops: key.usages.clone(),
..Default::default()
};
if key.key_type == CryptoKeyType::Private {
let private_key = key.raw.expanded_private_key();
let public_key =
crate::slhdsa::public_from_private(variant, private_key)
.map_err(|e| op_error(e.to_string()))?;
jwk.pub_field = Some(b64_url(&public_key));
jwk.priv_field = Some(b64_url(private_key));
} else {
jwk.pub_field = Some(b64_url(key.raw.bytes()));
}
Ok(ExportKeyOutput::Jwk(Box::new(jwk)))
}
_ => Err(not_supported(format!(
"Unsupported key format for SLH-DSA: {format:?}"
))),
}
}