use base64ct::{Base64, Base64UrlUnpadded, Encoding};
use sha2::{Digest, Sha512};
use crate::{error::AdesError, signer::Signer};
const RSA_OID: &str = "1.2.840.113549.1.1.1";
const EC_OID: &str = "1.2.840.10045.2.1";
pub(super) struct JwsComponents {
pub(super) header_b64url: String,
pub(super) payload_b64url: String,
pub(super) sig_b64url: String,
}
pub fn sign<S: Signer>(data: &[u8], signer: &S) -> Result<Vec<u8>, AdesError>
where
S::Error: std::error::Error + Send + Sync + 'static,
{
let c = sign_inner(data, signer)?;
Ok(assemble_jws(
&c.header_b64url,
&c.payload_b64url,
&c.sig_b64url,
r#"{"etsiU":[]}"#,
))
}
pub(super) fn sign_inner<S: Signer>(data: &[u8], signer: &S) -> Result<JwsComponents, AdesError>
where
S::Error: std::error::Error + Send + Sync + 'static,
{
let cert = signer.certificate();
let cert_der = cert.to_der();
let iat = unix_now()?;
let key_alg_oid = cert
.inner()
.tbs_certificate
.subject_public_key_info
.algorithm
.oid
.to_string();
let (alg, is_ecdsa) = match key_alg_oid.as_str() {
RSA_OID => ("RS256", false),
EC_OID => ("ES256", true),
_ => {
return Err(AdesError::NotImplemented(
"JAdES: unsupported key type — only RSA and EC are supported",
))
}
};
let cert_b64 = Base64::encode_string(cert_der);
let cert_sha512_b64url = Base64UrlUnpadded::encode_string(&Sha512::digest(cert_der));
let header_json = format!(
r#"{{"alg":"{alg}","cty":"application/octet-stream","iat":{iat},"typ":"jose+json","x5c":["{cert_b64}"],"x5t#o":{{"digAlg":"S512","digVal":"{cert_sha512_b64url}"}}}}"#
);
let header_b64url = Base64UrlUnpadded::encode_string(header_json.as_bytes());
let payload_b64url = Base64UrlUnpadded::encode_string(data);
let signing_input = format!("{header_b64url}.{payload_b64url}");
let digest = signer.digest_algorithm().hash(signing_input.as_bytes());
let raw_sig = signer
.sign_digest(&digest)
.map_err(|e| AdesError::Signer(Box::new(e)))?;
let sig_bytes = if is_ecdsa {
ecdsa_der_to_raw(&raw_sig)?
} else {
raw_sig
};
let sig_b64url = Base64UrlUnpadded::encode_string(&sig_bytes);
Ok(JwsComponents {
header_b64url,
payload_b64url,
sig_b64url,
})
}
pub(super) fn assemble_jws(
header_b64url: &str,
payload_b64url: &str,
sig_b64url: &str,
unprotected: &str,
) -> Vec<u8> {
format!(
r#"{{"payload":"{payload_b64url}","signatures":[{{"protected":"{header_b64url}","header":{unprotected},"signature":"{sig_b64url}"}}]}}"#
)
.into_bytes()
}
fn ecdsa_der_to_raw(der: &[u8]) -> Result<Vec<u8>, AdesError> {
let err = || AdesError::NotImplemented("JAdES: malformed ECDSA DER signature");
if der.len() < 6 || der[0] != 0x30 {
return Err(err());
}
let seq_len = der[1] as usize;
if der.len() < 2 + seq_len {
return Err(err());
}
let content = &der[2..2 + seq_len];
if content.len() < 2 || content[0] != 0x02 {
return Err(err());
}
let r_len = content[1] as usize;
if content.len() < 2 + r_len {
return Err(err());
}
let r_bytes = &content[2..2 + r_len];
let rest = &content[2 + r_len..];
if rest.len() < 2 || rest[0] != 0x02 {
return Err(err());
}
let s_len = rest[1] as usize;
if rest.len() < 2 + s_len {
return Err(err());
}
let s_bytes = &rest[2..2 + s_len];
let r = strip_leading_zero(r_bytes);
let s = strip_leading_zero(s_bytes);
let coord = r.len().max(s.len());
let mut out = vec![0u8; 2 * coord];
out[coord - r.len()..coord].copy_from_slice(r);
out[2 * coord - s.len()..].copy_from_slice(s);
Ok(out)
}
fn strip_leading_zero(b: &[u8]) -> &[u8] {
if b.first() == Some(&0x00) {
&b[1..]
} else {
b
}
}
fn unix_now() -> Result<u64, AdesError> {
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map(|d| d.as_secs())
.map_err(|_| AdesError::NotImplemented("system time before Unix epoch"))
}