ecamo 0.1.0

SSL image proxy with JWT authentication
Documentation
use elliptic_curve::sec1::ToEncodedPoint;

use crate::key_lookup::PublicKeyBucket;

fn default_prefix() -> String {
    ".ecamo".to_string()
}

fn default_max_redirects() -> u64 {
    0
}

fn default_timeout() -> u64 {
    10
}

fn default_max_length() -> u64 {
    5242880
}

fn default_token_lifetime() -> u64 {
    45
}

pub(crate) fn default_content_type_allowed() -> Vec<String> {
    vec![
        "image/avif".to_owned(),
        "image/bmp".to_owned(),
        "image/cgm".to_owned(),
        "image/g3fax".to_owned(),
        "image/gif".to_owned(),
        "image/heic".to_owned(),
        "image/heic-sequence".to_owned(),
        "image/heif".to_owned(),
        "image/heif-sequence".to_owned(),
        "image/ief".to_owned(),
        "image/jp2".to_owned(),
        "image/jpeg".to_owned(),
        "image/jpg".to_owned(),
        "image/pict".to_owned(),
        "image/png".to_owned(),
        "image/prs.btif".to_owned(),
        "image/svg+xml".to_owned(),
        "image/tiff".to_owned(),
        "image/vnd.adobe.photoshop".to_owned(),
        "image/vnd.djvu".to_owned(),
        "image/vnd.dwg".to_owned(),
        "image/vnd.dxf".to_owned(),
        "image/vnd.fastbidsheet".to_owned(),
        "image/vnd.fpx".to_owned(),
        "image/vnd.fst".to_owned(),
        "image/vnd.fujixerox.edmics-mmr".to_owned(),
        "image/vnd.fujixerox.edmics-rlc".to_owned(),
        "image/vnd.microsoft.icon".to_owned(),
        "image/vnd.ms-modi".to_owned(),
        "image/vnd.net-fpx".to_owned(),
        "image/vnd.wap.wbmp".to_owned(),
        "image/vnd.xiff".to_owned(),
        "image/webp".to_owned(),
        "image/x-cmu-raster".to_owned(),
        "image/x-cmx".to_owned(),
        "image/x-icon".to_owned(),
        "image/x-macpaint".to_owned(),
        "image/x-pcx".to_owned(),
        "image/x-pict".to_owned(),
        "image/x-portable-anymap".to_owned(),
        "image/x-portable-bitmap".to_owned(),
        "image/x-portable-graymap".to_owned(),
        "image/x-portable-pixmap".to_owned(),
        "image/x-quicktime".to_owned(),
        "image/x-rgb".to_owned(),
        "image/x-xbitmap".to_owned(),
        "image/x-xpixmap".to_owned(),
        "image/x-xwindowdump".to_owned(),
    ]
}

fn default_default_cache_control() -> String {
    "public, max-age=3600".to_string()
}

fn default_bind() -> String {
    "[::]:3000".to_string()
}

#[derive(serde::Deserialize, serde::Serialize, Clone)]
pub struct Config {
    #[serde(default = "default_bind")]
    pub bind: String,

    pub canonical_host: String,

    #[serde(with = "serde_with::json::nested")]
    pub private_keys: std::collections::HashMap<String, JwkObject>,
    #[serde(with = "serde_with::json::nested")]
    pub service_public_keys: std::collections::HashMap<String, JwkObject>,

    pub signing_kid: String,

    #[serde(default, with = "serde_regex")]
    pub service_host_regexp: Option<regex::Regex>,
    #[serde(default, with = "serde_regex")]
    pub source_allowed_regexp: Option<regex::Regex>,
    #[serde(default, with = "serde_regex")]
    pub source_blocked_regexp: Option<regex::Regex>,
    #[serde(default, with = "serde_regex")]
    pub private_source_allowed_regexp: Option<regex::Regex>,

    #[serde(default = "default_prefix")]
    pub prefix: String,
    #[serde(default = "default_max_redirects")]
    pub max_redirects: u64,
    #[serde(default = "default_token_lifetime")]
    pub token_lifetime: u64,
    #[serde(default = "default_timeout")]
    pub timeout: u64,
    #[serde(default = "default_max_length")]
    pub max_length: u64,

    #[serde(default = "default_content_type_allowed")]
    pub content_type_allowed: Vec<String>,

    pub auth_cookie: Option<String>,

    #[serde(default = "default_default_cache_control")]
    pub default_cache_control: String,
    #[serde(default)]
    pub default_surrogate_control: Option<String>,

    #[serde(default)]
    pub insecure: bool,
}

impl Config {
    // why jwt-simple doesn't provide a way to instantiate a key pair from ecdsa crate object?

    pub fn signing_key(&self) -> Result<jwt_simple::algorithms::ES256KeyPair, crate::error::Error> {
        let key = self
            .private_keys
            .get(&self.signing_kid)
            .ok_or_else(|| crate::error::Error::UnknownKeyError(self.signing_kid.clone()))?;
        let secret_key = elliptic_curve::SecretKey::<p256::NistP256>::from_jwk(&key.jwk)?;
        let signing_key = ecdsa::SigningKey::from(secret_key);

        // FIXME: implement convert traits for JwkObject

        Ok(
            jwt_simple::algorithms::ES256KeyPair::from_bytes(signing_key.to_bytes().as_ref())
                .unwrap()
                .with_key_id(&self.signing_kid),
        )
    }

    pub fn signing_decoding_keys(&self) -> PublicKeyBucket {
        make_jwk_hashmap(&self.private_keys)
    }

    pub fn service_decoding_keys(&self) -> PublicKeyBucket {
        make_jwk_hashmap(&self.service_public_keys)
    }

    pub fn auth_cookie_name(&self) -> &str {
        match &self.auth_cookie {
            Some(x) => x.as_ref(),
            None => {
                if self.insecure {
                    "ecamo_token"
                } else {
                    "__Host-ecamo_token"
                }
            }
        }
    }
}

fn make_jwk_hashmap(keys: &std::collections::HashMap<String, JwkObject>) -> PublicKeyBucket {
    keys.iter()
        .map(|(kid, jwk)| {
            let jwtkey: jwt_simple::algorithms::ES256PublicKey = jwk.try_into().unwrap();
            (kid.clone(), jwtkey)
        })
        .collect()
}

// XXX: to ignore use and kid field in given jwk
#[derive(Clone, serde::Deserialize, serde::Serialize)]
pub struct JwkObject {
    #[serde(rename = "use")]
    _use: Option<String>,
    kid: Option<String>,

    #[serde(flatten)]
    jwk: elliptic_curve::JwkEcKey,
}

impl std::convert::From<elliptic_curve::PublicKey<p256::NistP256>> for JwkObject {
    fn from(k: elliptic_curve::PublicKey<p256::NistP256>) -> JwkObject {
        JwkObject {
            _use: None,
            kid: None,
            jwk: k.into(),
        }
    }
}

impl std::convert::From<elliptic_curve::SecretKey<p256::NistP256>> for JwkObject {
    fn from(k: elliptic_curve::SecretKey<p256::NistP256>) -> JwkObject {
        JwkObject {
            _use: None,
            kid: None,
            jwk: k.into(),
        }
    }
}

impl std::convert::TryInto<jwt_simple::algorithms::ES256PublicKey> for JwkObject {
    type Error = jwt_simple::Error;
    fn try_into(self) -> Result<jwt_simple::algorithms::ES256PublicKey, jwt_simple::Error> {
        (&self).try_into()
    }
}

impl std::convert::TryInto<jwt_simple::algorithms::ES256PublicKey> for &JwkObject {
    type Error = jwt_simple::Error;
    fn try_into(self) -> Result<jwt_simple::algorithms::ES256PublicKey, jwt_simple::Error> {
        let public_key = elliptic_curve::PublicKey::<p256::NistP256>::from_jwk(&self.jwk)?;
        jwt_simple::algorithms::ES256PublicKey::from_bytes(
            public_key.to_encoded_point(false).as_bytes(),
        )
    }
}