use crate::error::JacsError;
use base64::{Engine as _, engine::general_purpose};
use serde::{Deserialize, Serialize};
use serde_json::{Value, json};
use std::error::Error;
use tracing::info;
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct Jwk {
pub kty: String,
pub kid: String,
pub alg: String,
pub use_: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub n: Option<String>, #[serde(skip_serializing_if = "Option::is_none")]
pub e: Option<String>, #[serde(skip_serializing_if = "Option::is_none")]
pub x: Option<String>, #[serde(skip_serializing_if = "Option::is_none")]
pub y: Option<String>, #[serde(skip_serializing_if = "Option::is_none")]
pub crv: Option<String>, }
pub struct DualKeyPair {
pub jacs_private_key: Vec<u8>,
pub jacs_public_key: Vec<u8>,
pub jacs_algorithm: String,
pub a2a_private_key: Vec<u8>,
pub a2a_public_key: Vec<u8>,
pub a2a_algorithm: String,
}
pub fn create_jwk_keys(
jacs_algorithm: Option<&str>,
a2a_algorithm: Option<&str>,
) -> Result<DualKeyPair, Box<dyn Error>> {
let jacs_alg = jacs_algorithm.unwrap_or("dilithium");
let a2a_alg = a2a_algorithm.unwrap_or("rsa");
info!(
"Generating ephemeral dual keys: JACS={}, A2A={}",
jacs_alg, a2a_alg
);
let (jacs_private, jacs_public) = match jacs_alg {
"dilithium" | "pq-dilithium" => crate::crypt::pq::generate_keys()?,
"rsa" => crate::crypt::rsawrapper::generate_keys()?,
"ring-Ed25519" => crate::crypt::ringwrapper::generate_keys()?,
"ecdsa" | "es256" => {
return Err(JacsError::CryptoError(
"ECDSA key generation for A2A is not yet implemented in this build".to_string(),
)
.into());
}
_ => {
return Err(JacsError::CryptoError(format!(
"Unsupported JACS algorithm: {}",
jacs_alg
))
.into());
}
};
let (a2a_private, a2a_public) = match a2a_alg {
"rsa" => crate::crypt::rsawrapper::generate_keys()?,
"ring-Ed25519" => crate::crypt::ringwrapper::generate_keys()?,
"ecdsa" | "es256" => {
return Err(JacsError::CryptoError(
"ECDSA key generation for A2A is not yet implemented in this build".to_string(),
)
.into());
}
_ => {
return Err(
JacsError::CryptoError(format!("Unsupported A2A algorithm: {}", a2a_alg)).into(),
);
}
};
Ok(DualKeyPair {
jacs_private_key: jacs_private,
jacs_public_key: jacs_public,
jacs_algorithm: jacs_alg.to_string(),
a2a_private_key: a2a_private,
a2a_public_key: a2a_public,
a2a_algorithm: a2a_alg.to_string(),
})
}
pub fn export_rsa_as_jwk(public_key: &[u8], key_id: &str) -> Result<Jwk, Box<dyn Error>> {
use rsa::traits::PublicKeyParts;
use rsa::{RsaPublicKey, pkcs1::DecodeRsaPublicKey, pkcs8::DecodePublicKey};
let pem_str = std::str::from_utf8(public_key)?;
let pem = pem::parse(pem_str)?;
let rsa_key = match RsaPublicKey::from_pkcs1_der(pem.contents()) {
Ok(k) => k,
Err(_) => RsaPublicKey::from_public_key_der(pem.contents())?,
};
let n = rsa_key.n();
let e = rsa_key.e();
let n_bytes = n.to_bytes_be();
let e_bytes = e.to_bytes_be();
let jwk = Jwk {
kty: "RSA".to_string(),
kid: key_id.to_string(),
alg: "RS256".to_string(),
use_: "sig".to_string(),
n: Some(general_purpose::URL_SAFE_NO_PAD.encode(&n_bytes)),
e: Some(general_purpose::URL_SAFE_NO_PAD.encode(&e_bytes)),
x: None,
y: None,
crv: None,
};
Ok(jwk)
}
pub fn export_ed25519_as_jwk(public_key: &[u8], key_id: &str) -> Result<Jwk, Box<dyn Error>> {
let key_bytes = match public_key.len() {
32 => public_key.to_vec(),
_ => {
return Err(JacsError::CryptoError(format!(
"Ed25519 public key must be 32 bytes, got {} bytes",
public_key.len()
))
.into());
}
};
Ok(Jwk {
kty: "OKP".to_string(),
kid: key_id.to_string(),
alg: "EdDSA".to_string(),
use_: "sig".to_string(),
n: None,
e: None,
x: Some(general_purpose::URL_SAFE_NO_PAD.encode(key_bytes)),
y: None,
crv: Some("Ed25519".to_string()),
})
}
pub fn export_as_jwk(
public_key: &[u8],
algorithm: &str,
key_id: &str,
) -> Result<Jwk, Box<dyn Error>> {
match algorithm {
"rsa" => export_rsa_as_jwk(public_key, key_id),
"ring-Ed25519" => export_ed25519_as_jwk(public_key, key_id),
"ecdsa" | "es256" => Err(JacsError::CryptoError(
"ECDSA JWK export is not yet implemented in this build".to_string(),
)
.into()),
_ => Err(JacsError::CryptoError(format!("Cannot export {} key as JWK", algorithm)).into()),
}
}
pub fn create_jwk_set(jwks: Vec<Jwk>) -> Value {
json!({
"keys": jwks
})
}
pub fn sign_jws(
payload: &[u8],
private_key: &[u8],
algorithm: &str,
key_id: &str,
) -> Result<String, Box<dyn Error>> {
let header = json!({
"alg": match algorithm {
"rsa" => "RS256",
"ring-Ed25519" => "EdDSA",
"ecdsa" | "es256" => return Err(JacsError::CryptoError("ECDSA JWS signing is not yet implemented in this build".to_string()).into()),
_ => return Err(JacsError::CryptoError(format!("Unsupported JWS algorithm: {}", algorithm)).into()),
},
"typ": "JWT",
"kid": key_id
});
let header_b64 = general_purpose::URL_SAFE_NO_PAD.encode(serde_json::to_vec(&header)?);
let payload_b64 = general_purpose::URL_SAFE_NO_PAD.encode(payload);
let signing_input = format!("{}.{}", header_b64, payload_b64);
let signature = match algorithm {
"rsa" => {
let sig_b64 =
crate::crypt::rsawrapper::sign_string(private_key.to_vec(), &signing_input)?;
general_purpose::STANDARD.decode(&sig_b64)?
}
"ring-Ed25519" => {
let sig_b64 =
crate::crypt::ringwrapper::sign_string(private_key.to_vec(), &signing_input)?;
general_purpose::STANDARD.decode(&sig_b64)?
}
"ecdsa" | "es256" => {
return Err(JacsError::CryptoError(
"ECDSA JWS signing is not yet implemented in this build".to_string(),
)
.into());
}
_ => {
return Err(
JacsError::CryptoError(format!("Unsupported algorithm: {}", algorithm)).into(),
);
}
};
let signature_b64 = general_purpose::URL_SAFE_NO_PAD.encode(&signature);
Ok(format!("{}.{}.{}", header_b64, payload_b64, signature_b64))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_create_jwk_set() {
let jwk = Jwk {
kty: "RSA".to_string(),
kid: "test-key".to_string(),
alg: "RS256".to_string(),
use_: "sig".to_string(),
n: Some("test_n".to_string()),
e: Some("AQAB".to_string()),
x: None,
y: None,
crv: None,
};
let jwk_set = create_jwk_set(vec![jwk]);
assert!(jwk_set["keys"].is_array());
}
}