use openssl::{
bn::{BigNum, BigNumContext},
ec::{EcGroup, EcKey},
ecdsa::EcdsaSig,
hash::{Hasher, MessageDigest},
nid::Nid,
pkey::Private,
};
use serde::{Serialize, Serializer};
use crate::{acme::utils, Error};
pub trait Key {
fn algorithm(&self) -> &str;
fn params(&self) -> &Params;
fn thumbprint(&self) -> &str;
fn sign(&self, header: &str, payload: &str) -> Result<Vec<u8>, Error>;
}
#[derive(Serialize)]
pub struct Params {
#[serde(flatten)]
internal: InternalParams,
}
impl Params {
fn new(internal: InternalParams) -> Self {
Self { internal }
}
}
#[derive(Serialize)]
#[serde(tag = "kty")]
enum InternalParams {
#[serde(rename = "EC")]
EllipticCurve(EllipticCurveParams),
}
impl From<EllipticCurveParams> for InternalParams {
fn from(params: EllipticCurveParams) -> Self {
Self::EllipticCurve(params)
}
}
#[derive(Clone, Serialize)]
pub struct EllipticCurveParams {
crv: &'static str,
x: String,
y: String,
}
pub struct ES256 {
key: EcKey<Private>,
params: Params,
thumbprint: String,
}
impl ES256 {
pub fn new() -> Result<Self, Error> {
let ec_group = EcGroup::from_curve_name(Nid::X9_62_PRIME256V1)?;
let ec_key = EcKey::generate(&ec_group)?;
let mut bn_context = BigNumContext::new()?;
let mut x = BigNum::new()?;
let mut y = BigNum::new()?;
let pub_key = ec_key.public_key();
pub_key.affine_coordinates(&ec_group, &mut x, &mut y, &mut bn_context)?;
let params = EllipticCurveParams {
crv: "P-256",
x: utils::base64url(&x.to_vec()),
y: utils::base64url(&y.to_vec()),
};
let thumbprint = format!(
r#"{{"crv":"{}","kty":"EC","x":"{}","y":"{}"}}"#,
params.crv, params.x, params.y
);
let mut hasher = Hasher::new(MessageDigest::sha256())?;
hasher.update(thumbprint.as_bytes())?;
let thumbprint = hasher.finish()?;
let res = Self {
key: ec_key,
params: Params::new(params.into()),
thumbprint: utils::base64url(&thumbprint),
};
Ok(res)
}
}
impl Key for ES256 {
fn algorithm(&self) -> &str {
"ES256"
}
fn params(&self) -> &Params {
&self.params
}
fn thumbprint(&self) -> &str {
&self.thumbprint
}
fn sign(&self, header: &str, payload: &str) -> Result<Vec<u8>, Error> {
let mut hasher = Hasher::new(MessageDigest::sha256())?;
hasher.update(header.as_bytes())?;
hasher.update(b".")?;
hasher.update(payload.as_bytes())?;
let hash = hasher.finish()?;
let signature = EcdsaSig::sign(&hash, &self.key)?;
let r = signature.r();
let s = signature.s();
let mut res = r.to_vec();
res.extend_from_slice(&s.to_vec());
Ok(res)
}
}
pub struct MessageBuilder<'a, K, P> {
key: &'a K,
payload: Option<&'a P>,
}
impl<'a, K> MessageBuilder<'a, K, Empty> {
pub fn new(key: &'a K) -> Self {
Self { key, payload: None }
}
}
impl<'a, K, P> MessageBuilder<'a, K, P> {
pub fn with_payload<T>(self, payload: &'a T) -> MessageBuilder<'a, K, T> {
MessageBuilder {
key: self.key,
payload: Some(payload),
}
}
}
impl<'a, K, P> MessageBuilder<'a, K, P>
where
K: Key,
P: Serialize,
{
pub fn build_with_jwk_header(&self, url: &str, nonce: &str) -> Result<Message, Error> {
let header = Header {
alg: self.key.algorithm(),
nonce,
url,
jwk: Some(self.key.params()),
kid: None,
};
self.build(header)
}
pub fn build_with_kid_header(
&self,
kid: &str,
url: &str,
nonce: &str,
) -> Result<Message, Error> {
let header = Header {
alg: self.key.algorithm(),
nonce,
url,
jwk: None,
kid: Some(kid),
};
self.build(header)
}
fn build(&self, header: Header<'_>) -> Result<Message, Error> {
let header = serde_json::to_vec(&header).map_err(|err| {
Error::from_static_msg_and_cause("unable to serialize a JWS header", err)
})?;
let header = utils::base64url(&header);
let payload = if let Some(payload) = self.payload {
serde_json::to_vec(payload).map_err(|err| {
Error::from_static_msg_and_cause("unable to serialize a given payload", err)
})?
} else {
Vec::new()
};
let payload = utils::base64url(&payload);
let signature = self.key.sign(&header, &payload)?;
let signature = utils::base64url(&signature);
let res = Message {
protected: header,
payload,
signature,
};
Ok(res)
}
}
#[derive(Serialize)]
pub struct Message {
protected: String,
payload: String,
signature: String,
}
#[derive(Serialize)]
struct Header<'a> {
alg: &'a str,
nonce: &'a str,
url: &'a str,
#[serde(skip_serializing_if = "Option::is_none")]
jwk: Option<&'a Params>,
#[serde(skip_serializing_if = "Option::is_none")]
kid: Option<&'a str>,
}
pub struct Empty;
impl Serialize for Empty {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_unit()
}
}