signer-core 0.1.0

Signer core package.
Documentation
use base64::{prelude::BASE64_URL_SAFE, Engine};
use serde::{Deserialize, Serialize};

use crate::{signer_signature::SignerSignature, signer_user::SignerUser};

#[derive(Debug, Serialize, Deserialize)]
pub struct SignerJWT {
  pub header: SignerJWTHeader,
  pub claims: SignerJWTClaims,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct SignerJWTHeader {
  pub typ: String,
  pub alg: String,
  pub jwk: SignerJWK,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct SignerJWTClaims {
  pub aud: String,
  pub jti: String,
  pub iat: i64,
  pub exp: i64,
  pub nbf: i64,
  pub iss: String,
  pub sub: String,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct SignerJWK {
  pub kty: String,
  pub alg: String,
  pub crv: String,
  pub x: String,
}

impl SignerJWT {
  pub fn new(header: SignerJWTHeader, claims: SignerJWTClaims) -> Self {
    Self { header, claims }
  }

  pub fn decode_unverify(str: &str) -> anyhow::Result<Self> {
    let split: Vec<&str> = str.split(".").collect();
    let header: SignerJWTHeader =
      serde_json::from_slice(&BASE64_URL_SAFE.decode(split[0].as_bytes())?)?;
    let claims: SignerJWTClaims =
      serde_json::from_slice(&BASE64_URL_SAFE.decode(split[1].as_bytes())?)?;

    Ok(SignerJWT::new(header, claims))
  }

  pub fn decode(str: &str) -> anyhow::Result<Self> {
    let split: Vec<&str> = str.split(".").collect();
    let header: SignerJWTHeader =
      serde_json::from_slice(&BASE64_URL_SAFE.decode(split[0].as_bytes())?)?;
    let claims: SignerJWTClaims =
      serde_json::from_slice(&BASE64_URL_SAFE.decode(split[1].as_bytes())?)?;

    let base = [split[0], split[1]].join(".");
    let sig_bytes = split[2];

    let sig = SignerSignature {
      bytes: sig_bytes.to_string(),
      pub_key: header.jwk.x.clone(),
    };

    sig.verify(base.as_bytes())?;

    Ok(SignerJWT::new(header, claims))
  }

  pub fn encode(&self, u: &SignerUser) -> anyhow::Result<String> {
    let base = [
      BASE64_URL_SAFE.encode(serde_json::to_string(&self.header)?.as_bytes()),
      BASE64_URL_SAFE.encode(serde_json::to_string(&self.claims)?.as_bytes()),
    ]
    .join(".");

    let sig = SignerSignature::create(u, base.as_bytes())?;

    Ok([base, sig.bytes].join("."))
  }
}

impl SignerJWTClaims {
  pub fn default(aud: String, jti: String) -> Self {
    let now = chrono::Local::now();
    Self {
      aud,
      jti,
      iat: now.timestamp_millis(),
      exp: now
        .checked_add_days(chrono::Days::new(7))
        .unwrap()
        .timestamp_millis(),
      nbf: now.timestamp_millis(),
      iss: format!(""),
      sub: format!(""),
    }
  }

  pub fn with_issuer(mut self, issuer: &str) -> Self {
    self.iss = issuer.to_string();
    self
  }

  pub fn with_expired_duration(
    mut self,
    expired_time: chrono::Duration,
  ) -> Self {
    self.exp = chrono::DateTime::from_timestamp_millis(self.iat)
      .unwrap()
      .checked_add_signed(expired_time)
      .unwrap()
      .timestamp_millis();
    self
  }

  pub fn with_subject(mut self, subject: &str) -> Self {
    self.sub = subject.to_string();
    self
  }
}

impl SignerJWTHeader {
  pub fn default(u: &SignerUser) -> Self {
    Self {
      typ: format!("JWT"),
      alg: format!("Schnorr SHA3-256"),
      jwk: SignerJWK::default(u),
    }
  }
}

impl SignerJWK {
  fn default(u: &SignerUser) -> Self {
    Self {
      kty: format!("OKP"),
      alg: format!("schnorr"),
      crv: format!("sr25519"),
      x: u.public.pub_key.clone(),
    }
  }
}