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!(); } }