1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
//! # Mio License
//!
//! `mio_license` will check Media-IO license products.
//!
//! ## Architecture
//!
//! It's based on AWS license validation (using [V4 signature](https://docs.aws.amazon.com/fr_fr/general/latest/gr/sigv4-create-string-to-sign.html))  
//! In that model, 3 parts are involved:
//! - the licensed Media-IO project
//! - a license validation (here the Support Platform)
//! - the signer
//!
//! This model is use due to the Player mode who runs in the web browser and interfaced with Javascript.
//! So any body can access to the license passed to the player, so it can be very easy to hack our products with that.
//!
//! ### Targets
//!
//! The library can be use on every platform (OSX, Linux/Unix, Windows), but it also requires to works on WebAssembly target.
//!
//! ### Generated data
//!
//! To understand the mechaniscm, it requires first to describe what data is generated and where it's stored.
//!
//! On the *Support platform*, a `secret key` is the based to generate hashed licenses.  
//! Our licenses use the [JWT model](https://jwt.io/), simple and support in all languages
//! A `private key` is generated at the same time, a random string.
//!
//!
//! Each *Media-IO product* is build using this library.
//! It's used in the product to validate the JWT license.
//! So each product needs to provide an API to pass:
//! - the JWT license
//! - the signer URL
//!
//! For the *signer*, it requires to start with the `private key`.
//!
//! ### Validation process
//!
//! To validate a license, many steps are needed.
//!
//! 1. *Media-IO product* get the JWT license.
//! 2. *Media-IO product* retrieve Claims from the JWT license.
//! 2. *Media-IO product* validate product with license product list.
//! 2. *Media-IO product* validate the domain name, for native platforms it will check is the license is not for a domain name.
//! 3. *Media-IO product* generate a `datetime` using the format: `YYYYMMDD'T'HHMMSS'Z'`.
//! 4. *Media-IO product* send a request to the signer to generate the signature.
//! 5. *Signer* will get `datetime`, JWT license and the `private key` to generate the `signature`.
//! 6. *Media-IO product* send a request to the *Support Platform* with the `signature`, the `JWT license` and the datetime.
//! 7. *Support platform* generate the same signature based on same information and compare the result (including an allowed delta time between the `datetime` and the current time)
//! 8. *Media-IO product* is now validated or not !
//!
//! ## Security
//!
//! The important thing to undestand here about the security is the fact of `datetime` inclusion in the hash signature information.  
//! With that requests have a validity duration, so it's difficult to hack.

#[macro_use]
extern crate serde_derive;
#[macro_use]
extern crate serde_json;
#[macro_use]
extern crate log;

mod claims;
mod messages;
mod sign_license;
mod socket_url;
mod time;
#[cfg(not(target_arch = "wasm32"))]
mod validate_license;
#[cfg(target_arch = "wasm32")]
mod wasm_validate_license;

use crate::time::get_now_utc_string;
pub use claims::Claims;
use std::str::FromStr;

#[cfg(not(target_arch = "wasm32"))]
pub use validate_license::{MioWebSocket, MioWebSocketStream};

#[cfg(target_arch = "wasm32")]
use wasm_bindgen::prelude::*;
#[cfg(target_arch = "wasm32")]
pub use {wasm_validate_license::validate_license, wasm_validate_license::MioWebSocket};

#[cfg(not(target_arch = "wasm32"))]
pub use validate_license::validate_license;

#[cfg(target_arch = "wasm32")]
#[wasm_bindgen]
pub fn init_panic_hook() {
  console_error_panic_hook::set_once();
}

#[cfg(target_arch = "wasm32")]
#[wasm_bindgen]
pub async fn web_validate(
  token: String,
  product: String,
  signer_url: String,
) -> Result<JsValue, JsValue> {
  console_log::init_with_level(log::Level::Debug).unwrap();
  match validate(&token, &product, &signer_url).await {
    Err(error) => Err(JsValue::from_str(&error)),
    Ok((_claims, web_socket)) => Ok(JsValue::from(web_socket)),
  }
}

/// Validate a token for a Media-IO product
pub async fn validate(
  token: &str,
  product: &str,
  signer_url: &str,
) -> Result<(Claims, MioWebSocket), String> {
  let claims = Claims::from_str(token)?;
  debug!("License claims: {:?}", claims);

  claims.check_product(product)?;
  claims.check_domain_name()?;

  let utc_datetime = get_now_utc_string();

  let signed_token = sign_license::sign_license(signer_url, token, &utc_datetime).await?;

  let base_url = claims.get_base_url();

  let url = socket_url::generate_websocket_url(&base_url, &utc_datetime, &signed_token, &token);

  debug!("License base URL: {}", base_url);
  let ws_stream = validate_license(&url, product).await?;

  Ok((claims, ws_stream))
}

pub trait MioWebSocketSend {
  fn send<S: serde::Serialize>(self, _payload: S) -> Self
  where
    Self: std::marker::Sized,
  {
    unimplemented!();
  }
}